### 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 động (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` để thiết lập 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 **đã từng 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 người.
:::
Hãy chờ callback hoàn thành `identify` được kích hoạt trước khi gọi các phương thức SDK khác. Các cuộc gọi đồng thời có thể rơi vào hồ sơ người dùng ẩn danh thay vì hồ sơ người dùng đã xác định. Xem [Thứ tự gọi trong Android SDK](android-sdk-call-order).
tùy chọn
mặc định: `en`
|Định danh của [bản địa hóa paywall](add-paywall-locale-in-adapty-paywall-builder). Tham số này cần là mã ngôn ngữ gồm một hoặc hai thẻ con phân tách bằng ký tự gạch ngang (**-**). 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ã 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.
| | **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 đã cache trong trường hợp thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng 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 sẽ có thời gian tải nhanh hơn, bất kể kết nối internet của họ có chập chờn đến đâu. Cache được cập nhật thường xuyên, nên an toàn khi sử dụng trong suốt phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt ứng dụng hoặc dọn dẹp thủ công.
Adapty SDK lưu trữ paywalls 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 dùng CDN để tải paywalls 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 paywalls 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 `loadTimeout` đã chỉ định, vì thao tác có thể bao gồm nhiều yêu cầu bên dưới.
Đối với Android: Bạn có thể tạo `TimeInterval` với các hàm mở rộng (như `5.seconds`, trong đó `.seconds` được import từ `import com.adapty.utils.seconds`), hoặc `TimeInterval.seconds(5)`. Để không giới hạn, sử dụng `TimeInterval.INFINITE`.
| Tham số phản hồi: | Tham số | Mô tả | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Một đối tượng [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) với 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 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 Đảm bảo bật toggle **Show on device** trong paywall builder. Nếu tùy chọn này chưa đượ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ó chứa `ViewConfiguration` không — điều này cho biết paywall được tạo bằng Paywall Builder. Thông tin này sẽ hướng dẫn bạn cách hiển thị paywall. Nếu có `ViewConfiguration`, hãy xử lý nó như một paywall Paywall Builder; nếu không, [xử lý nó như một remote config paywall](present-remote-config-paywalls).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 cần là mã ngôn ngữ gồm một hoặc nhiều thẻ con phân tách bằng ký tự gạch ngang (**-**). 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ã 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.
| | **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 đã cache trong trường hợp thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng 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 sẽ có thời gian tải nhanh hơn, bất kể kết nối internet của họ có chập chờn đến đâu. Cache được cập nhật thường xuyên, nên an toàn khi sử dụng trong suốt phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt ứng dụng hoặc dọn dẹp thủ công.
| ## Tùy chỉnh assets \{#customize-assets\} Để tùy chỉnh hình ảnh và video trong paywall, hãy triển khai các custom assets. Hình ảnh hero và video có các ID được định nghĩa sẵn: `hero_image` và `hero_video`. Trong một bundle custom asset, 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 ID tùy chỉnh](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 chạy video. :::important Để sử dụng tính năng này, hãy cập nhật Adapty Android SDK lên phiên bản 3.7.0 trở lên. ::: Đâ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: ```kotlin showLineNumbers val customAssets = AdaptyCustomAssets.of( "hero_image" to AdaptyCustomImageAsset.remote( url = "https://example.com/image.jpg", preview = AdaptyCustomImageAsset.file( FileLocation.fromAsset("images/hero_image_preview.png"), ) ), "hero_video" to AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), ), ) val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, customAssets, ) ``` :::note Nếu không tìm thấy asset, paywall sẽ quay về giao diện mặc định của nó. ::: --- # File: android-present-paywalls --- --- title: "Android - Hiển thị paywall mới với Paywall Builder" description: "Tìm hiểu cách hiển thị paywall trên Android để tối ưu hóa monetization." --- 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 cần hiển thị lẫn cách hiển thị. :::warning Hướng dẫn này chỉ dành cho **paywall Paywall Builder mới** yêu cầu SDK v3.0. Quy trình hiển thị paywall khác nhau tùy theo phiên bản Paywall Builder được sử dụng, paywall remote config, và [chế độ Observer](observer-vs-full-mode). - Để hiển thị **paywall Remote config**, xem [Render paywall được thiết kế bằng remote config](present-remote-config-paywalls). - Để hiển thị **paywall ở chế độ Observer**, xem [Android - Hiển thị paywall Paywall Builder trong chế độ Observer](android-present-paywall-builder-paywalls-in-observer-mode) ::: Để lấy đối tượng `viewConfiguration` được dùng bên dưới, xem [Fetch paywall Paywall Builder và cấu hình của chúng](android-get-pb-paywalls).Insets là các khoảng cách xung quanh paywall để ngăn các phần tử tương tác bị ẩn sau thanh hệ thống.
Mặc định: `UNSPECIFIED` — Adapty sẽ tự động điều chỉnh insets, phù hợp với paywall edge-to-edge.
Nếu paywall của bạn không phải edge-to-edge, bạn có thể muốn đặt insets tùy chỉnh. Cách thực hiện được mô tả trong phần [Thay đổi insets paywall](android-present-paywalls#change-paywall-insets) bên dưới.
| | **personalizedOfferResolver** | tùy chọn | Để chỉ định giá cá nhân hóa ([đọc thêm](https://developer.android.com/google/play/billing/integrate#personalized-price)), hãy implement `AdaptyUiPersonalizedOfferResolver` và truyền logic của bạn để ánh xạ `AdaptyPaywallProduct` thành `true` nếu giá sản phẩm được cá nhân hóa, ngược lại là `false`. | | **tagResolver** | tùy chọn | Dùng `AdaptyUiTagResolver` để resolve các custom tag trong văn bản paywall. Resolver này nhận tham số tag và resolve thành chuỗi tương ứng. Tham khảo chủ đề Custom tags trong Paywall Builder để biết thêm chi tiết. | | **timerResolver** | tùy chọn | Truyền resolver vào đây nếu bạn sử dụng chức năng timer tùy chỉnh. | :::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. ::: ## Thay đổi insets paywall \{#change-paywall-insets\} Insets là các khoảng cách xung quanh paywall để ngăn các phần tử tương tác bị ẩn sau thanh hệ thống. Mặc định, Adapty sẽ tự động điều chỉnh insets, phù hợp với paywall edge-to-edge. Nếu paywall của bạn không phải edge-to-edge, bạn có thể muốn đặt insets tùy chỉnh: - Nếu cả thanh trạng thái lẫn thanh điều hướng đều không che `AdaptyPaywallView`, dùng `AdaptyPaywallInsets.NONE`. - Với các cấu hình tùy chỉnh hơn, ví dụ nếu paywall của bạn chồng lên thanh trạng thái trên cùng nhưng không chồng lên phần dưới, bạn có thể chỉ đặt `bottomInset` thành `0` như ví dụ bên dưới:
## 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**: Bạn có thể đang gọi `logShowPaywall` trong code, điều này làm nhân đôi số lượt xem 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, vì vậy 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 khi đang sử dụng Paywall Builder.
## Các vấn đề khác \{#other-issues\}
**Vấn đề**: Bạn đang gặp các sự cố liên quan đến Paywall Builder chưa đượ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](android-sdk-migration-guides) nếu cần. Nhiều vấn đề đã được giải quyết trong các phiên bản SDK mới hơn.
---
# File: android-quickstart-manual
---
---
title: "Bật tính năng mua hàng trong custom paywall trên Android SDK"
description: "Tích hợp Adapty SDK vào custom paywall Android để 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 custom paywall của bạn. Bạn 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 mới và khôi phục giao dịch cũ.
:::important
**Hướng dẫn này dành cho các nhà phát triển đang triển khai custom paywall.** Nếu bạn muốn cách đơn giản nhất để bật tính năng mua hàng, hãy sử dụng [Adapty Paywall Builder](android-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 cứ 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 thay đổi sản phẩm, giá cả và ưu đãi mà không cần chỉnh sửa code ứng dụng.
- [**Placement**](placements) – nơi và thời điểm bạn hiển thị paywall trong ứng dụng (như `main`, `onboarding`, `settings`). Bạn thiết lập paywall cho các placement trên dashboard, sau đó gọi 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ị 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 làm việc với custom paywall. Về cơ bản, đây chỉ là cách bạn quản lý các sản phẩm bán trong ứng dụng.
Để triển khai custom paywall, 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ì cần làm trên dashboard, hãy theo dõi hướng dẫn khởi đầ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 mình.
Tuy nhiên, Adapty SDK xử lý người dùng ẩn danh và người dùng đã xác định theo các cách khác nhau. Đọc [hướng dẫn khởi đầu nhanh về nhận dạng người dùng](android-quickstart-identify) để hiểu rõ sự khác biệt 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 custom paywall, 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`.
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 được xác định là mã ngôn ngữ gồm một hoặc nhiều thẻ con phân cách bởi dấu trừ (**-**). Thẻ đầu tiên là ngôn ngữ, thẻ 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ã locale](android-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.
| | **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 đã lưu trong cache nếu thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng 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 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 sẽ trải nghiệm thời gian tải nhanh hơn bất kể chất lượng kết nối. Cache được cập nhật thường xuyên, nên an toàn khi dùng trong phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn sau 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 dọn dẹp thủ công.
Adapty SDK lưu trữ paywall theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và [paywall dự phòng](android-use-fallback-paywalls). Chúng tôi cũng dùng CDN để lấy paywall nhanh hơn và một server dự phòng riêng 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 paywall đồng thời đảm bảo độ tin cậy ngay cả khi kết nối internet yếu.
| | **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ị đã 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.
| Đừng hardcode ID sản phẩm! Vì paywall được cấu hình từ xa, các sản phẩm khả dụng, số lượng sản phẩm và ư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 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 nên hiển thị cả 3 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 | Đối tượng [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) với: 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ó: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 được xác định là mã ngôn ngữ gồm một hoặc nhiều thẻ con phân cách bởi dấu trừ (**-**). Thẻ đầu tiên là ngôn ngữ, thẻ 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ã locale](android-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.
| | **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 đã lưu trong cache nếu thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng 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 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 sẽ trải nghiệm thời gian tải nhanh hơn bất kể chất lượng kết nối. Cache được cập nhật thường xuyên, nên an toàn khi dùng trong phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn sau 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 dọn dẹp thủ công.
| --- # File: present-remote-config-paywalls-android --- --- title: "Hiển thị paywall được thiết kế bằng Remote Config trong Android SDK" description: "Khám phá cách hiển thị paywall Remote Config trong Adapty Android 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ự implement phần hiển thị trong code của ứng dụng để người dùng có thể thấy nó. Vì Remote Config linh hoạt và tùy biến theo nhu cầu của bạn, bạn hoàn toàn kiểm soát những gì được đưa vào và giao diện paywall sẽ trông như thế nào. Chúng tôi cung cấp một phương thức để lấy cấu hình remote, 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à trích xuất các giá trị cần thiết.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://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) cung cấp thông tin toàn diện 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 gói đăng ký mới thay vì gia hạn gói hiện tại, cách thức hoạt động phụ thuộc vào cửa hàng. Với Google Play, gói đăng ký không được cập nhật tự động. Bạn cần xử lý việc chuyển đổi trong code của ứng dụng như mô tả bên dưới. Để thay thế gói đăng ký bằng gói khác trên Android, hãy gọi phương thức `.makePurchase()` với tham số bổ sung:Đối tượng [`AdaptyProfile`](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). 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-android --- --- title: "Triển khai Observer mode trong Android 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 Android 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ể khám phá [Observer mode](observer-vs-full-mode). Ở dạng cơ bản, Observer Mode cung cấp phân tích 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 [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk). 2. [Báo cáo giao dịch](report-transactions-observer-mode-android) từ hạ tầng mua hàng hiện có của bạn sang Adapty. ## Thiết lập 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 đó. :::1. Triển khai `AdaptyUiObserverModeHandler`. Sự kiện `onPurchaseInitiated` sẽ thông báo cho bạn khi người dùng bắt đầu thực hiện mua hàng. Bạn có thể kích hoạt flow mua hàng tùy chỉnh của mình trong callback này:
Với iOS, StoreKit1: đối tượng [`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction).
Với iOS, StoreKit 2: đối tượng [Transaction](https://developer.apple.com/documentation/storekit/transaction).
Với Android: Chuỗi định danh (`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.
|Với iOS, StoreKit1: đối tượng [`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction).
Với iOS, StoreKit 2: đối tượng [Transaction](https://developer.apple.com/documentation/storekit/transaction).
Với Android: Chuỗi định danh (`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.
| Đối với chế độ toàn màn hình khi system bar che một phần giao diện, lấy insets theo cách sau:phoneNumber
firstName
lastName
| String | | gender | Enum, các giá trị được phép: `female`, `male`, `other` | | birthday | Date | ### Thuộc tính người dùng tùy chỉnh \{#custom-user-attributes\} Bạn có thể thiết lập 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ụ, đối với ứng dụng thể dục, có thể là số buổi tập mỗi tuần; đối 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 sử dụng trong analytics để tìm ra các chỉ số sản phẩm nào ảnh hưởng nhiều nhất đến doanh thu.Một đối tượng [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Thông thường, bạn chỉ cần kiểm tra trạng thái mức độ truy cập của profile để xác định xem 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ả mới nhất vì nó luôn cố gắng 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ừ server, dữ liệu từ cache sẽ được trả về. Cần lưu ý rằng Adapty SDK cập nhật cache `AdaptyProfile` định kỳ để đảm bảo thông tin luôn được cập nhật nhất có thể.
| 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. Một ứng dụng có thể có nhiều mức độ truy cập. 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 duy nhất — khi đó, bạn có thể dùng mức độ truy cập mặc định là "premium". Dưới đây là ví dụ kiểm tra mức độ truy cập "premium" mặc định:tùy chọn
mặc định: `en`
|Mã định danh của bản địa hóa onboarding. 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 cách bằng dấu trừ (**-**). Subtag đầu tiên là ngôn ngữ, subtag thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).
Xem [Localizations and locale codes](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 đã lư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 xem xét 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ó tốc độ 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 để 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 sau 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ữ các onboarding cục bộ theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và các 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 onboarding trong khi vẫn đảm bảo độ tin cậy ngay cả trong trường hợp 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 thời gian đượ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.
Với Android: Bạn có thể tạo `TimeInterval` bằng các extension function (như `5.seconds`, trong đó `.seconds` từ `import com.adapty.utils.seconds`), hoặc `TimeInterval.seconds(5)`. Để không giới hạn, sử dụng `TimeInterval.INFINITE`.
| Tham số phản hồi: | Tham số | Mô tả | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Một đối tượng [`AdaptyOnboarding`](https://android.adapty.io/adapty/com.adapty.models/-adapty-onboarding/) với: mã định danh và cấu hình onboarding, Remote Config, và một số thuộc tính khác. | ## 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 đó, 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à hơn là không hiển thị onboarding nào cả. Để giải quyết vấn đề 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 đã 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ể gây ra sự cố 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 đúng. - **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 tố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 dùng `getOnboardingForDefaultAudience` như bên dưới. Nếu không, hãy sử dụng `getOnboarding` như được mô tả [ở trên](#fetch-onboarding). ::: ```kotlin Adapty.getOnboardingForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val onboarding = result.value // Handle successful onboarding retrieval } is AdaptyResult.Error -> { val error = result.error // Handle error case } } } ``` 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 onboarding. 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 cách bằng dấu trừ (**-**). Subtag đầu tiên là ngôn ngữ, subtag thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).
Xem [Localizations and locale codes](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 đã lư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 xem xét 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ó tốc độ 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 để 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 sau 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ữ các onboarding cục bộ theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và các 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 onboarding trong khi vẫn đảm bảo độ tin cậy ngay cả trong trường hợp kết nối internet kém.
| --- # File: android-present-onboardings --- --- title: "Hiển thị onboarding trong Android SDK" description: "Tìm hiểu cách hiển thị onboarding trên Android để tăng mức độ tương tác với người dùng." --- Trước khi bắt đầu, hãy đảm bảo rằng: 1. Bạn đã cài đặt [Adapty Android SDK](sdk-installation-android) phiên bản 3.8.0 trở lên. 2. Bạn đã [tạo onboarding](create-onboarding). 3. Bạn đã thêm onboarding vào một [placement](placements). Nếu bạn đã tùy chỉnh onboarding bằng Onboarding 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. Onboarding đó đã bao gồm cả nội dung hiển thị lẫn cách thức hiển thị. Để hiển thị onboarding trực quan trên màn hình thiết bị, trước tiên bạn phải cấu hình nó. Để làm điều này, gọi phương thức `AdaptyUI.getOnboardingView()` hoặc tạo `OnboardingView` trực tiếp:
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 delegate `onCustomAction` sẽ được kích hoạt với action ID từ builder. Bạn có thể tự tạo ID, chẳng hạn như "allowNotifications".
```kotlin showLineNumbers
class YourActivity : AppCompatActivity() {
private val eventListener = object : AdaptyOnboardingEventListener {
override fun onCustomAction(action: AdaptyOnboardingCustomAction, context: Context) {
when (action.actionId) {
"allowNotifications" -> {
// Request notification permissions
}
}
}
override fun onError(error: AdaptyOnboardingError, context: Context) {
// Handle errors
}
// ... other required delegate methods
}
}
```
JSON của paywall dự phòng cục bộ không hợp lệ.
Hãy sửa paywall tiếng Anh mặc định trước, sau đó thay thế các paywall cục bộ không hợp lệ. Tham khảo chủ đề [Tùy chỉnh paywall bằng Remote Config](customize-paywall-with-remote-config) để biết cách sửa paywall, và [Định nghĩa paywall dự phòng cục bộ](fallback-paywalls) để biết cách thay thế các paywall cục bộ.
| |CURRENT_SUBSCRIPTION_TO_UPDATE
\_NOT_FOUND_IN_HISTORY
| Không tìm thấy gói đăng ký gốc cần thay thế trong danh sách gói đăng ký đang hoạt động. | | [BILLING_SERVICE_TIMEOUT](https://developer.android.com/google/play/billing/errors#service_timeout_error_code_-3) | Lỗi này cho biết yêu cầu đã vượt quá thời gian chờ tối đa trước khi Google Play có thể phản hồi. Nguyên nhân có thể là do thao tác được yêu cầu bởi lệnh gọi Play Billing Library bị trễ. | | [FEATURE_NOT_SUPPORTED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#FEATURE_NOT_SUPPORTED()) | Tính năng được yêu cầu không được Play Store hỗ trợ trên thiết bị hiện tại. | | [BILLING_SERVICE_DISCONNECTED](https://developer.android.com/google/play/billing/errors#service_disconnected_error_code_-1) | Lỗi này cho biết kết nối của ứng dụng khách đến dịch vụ Google Play Store thông qua `BillingClient` đã bị ngắt. | | [BILLING_SERVICE_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#service_unavailable_error_code_2) | Lỗi này cho biết dịch vụ Google Play Billing hiện không khả dụng. Trong hầu hết các trường hợp, điều này có nghĩa là có sự cố kết nối mạng ở đâu đó giữa thiết bị khách và các dịch vụ Google Play Billing. | | [BILLING_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) |Lỗi này cho biết đã xảy ra sự cố thanh toán trong quá trình mua hàng. Các nguyên nhân có thể bao gồm:
1. Ứng dụng Play Store trên thiết bị của người dùng bị thiếu hoặc đã lỗi thời.
2. Người dùng ở quốc gia không được hỗ trợ.
3. Người dùng thuộc tài khoản doanh nghiệp mà quản trị viên đã tắt tính năng mua hàng.
4. Google Play không thể tính phí phương thức thanh toán của người dùng (ví dụ: thẻ tín dụng đã hết hạn).
5. Người dùng chưa đăng nhập vào ứng dụng Play Store.
| | [DEVELOPER_ERROR](https://developer.android.com/google/play/billing/errors#developer_error) | Lỗi này cho biết bạn đang sử dụng API không đúng cách. | | [BILLING_ERROR](https://developer.android.com/google/play/billing/errors#error_error_code_6) | Lỗi này cho biết có sự cố nội bộ với chính Google Play. | | [ITEM_ALREADY_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_ALREADY_OWNED()) | Sản phẩm đã được mua trước đó. | | [ITEM_NOT_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_NOT_OWNED()) | Lỗi này cho biết thao tác được yêu cầu trên mặt hàng thất bại vì người dùng chưa sở hữu nó. | | [BILLING_NETWORK_ERROR](https://developer.android.com/google/play/billing/errors#network_error_error_code_12) | Lỗi này cho biết có sự cố với kết nối mạng giữa thiết bị và hệ thống của Play. | | NO_PRODUCT_IDS_FOUND |Lỗi này cho biết không có sản phẩm nào trong paywall khả dụng trong cửa hàng.
Nếu bạn gặp lỗi này, hãy làm theo các bước dưới đây để khắc phục:
Nếu mã hết hạn trước khi bạn ủy quyền, hoặc nếu bạn nhấp vào **Deny**, hãy chạy lại lệnh sau để khởi động lại flow:
```bash
adapty auth login
```
## Quản lý xác thực \{#manage-authentication\}
### Kiểm tra trạng thái xác thực \{#check-authentication-status\}
Để xem trạng thái xác thực hiện tại, chạy:
```bash
adapty auth status
```
Khi đã xác thực, kết quả hiển thị email, tiền tố token đã che, và đường dẫn đến tệp cấu hình cục bộ:
```
Email: you@example.com
Token: abcd1234****
Config: ~/.config/adapty/config.json
```
Khi chưa xác thực:
```
Not authenticated. Run `adapty auth login`.
```
### Xác minh token của bạn \{#verify-your-token\}
Để xác nhận token hợp lệ và xem thông tin tài khoản, chạy:
```bash
adapty auth whoami
```
Khác với `adapty auth status`, lệnh này thực hiện một yêu cầu trực tiếp đến máy chủ để xác minh token.
### Đăng xuất \{#log-out\}
Để xóa thông tin đăng nhập đã lưu trữ cục bộ, chạy:
```bash
adapty auth logout
```
Lệnh này xóa `~/.config/adapty/config.json`. Token vẫn còn hiệu lực phía máy chủ cho đến khi hết hạn — nếu bạn cần vô hiệu hóa ngay lập tức, hãy dùng `adapty auth revoke` thay thế.
### Thu hồi token \{#revoke-your-token\}
Để vô hiệu hóa token trên máy chủ và xóa nó cục bộ, chạy:
```bash
adapty auth revoke
```
Dùng lệnh này khi bạn muốn vô hiệu hóa hoàn toàn một token — ví dụ, nếu thông tin đăng nhập của bạn có thể đã bị lộ. Sau khi thu hồi, chạy `adapty auth login` để xác thực lại.
## Lỗi token \{#token-errors\}
Nếu token bị thu hồi hoặc không còn hợp lệ, các lệnh CLI trả về lỗi 401. Để xác thực lại, chạy:
```bash
adapty auth login
```
---
# File: developer-cli-reference
---
---
title: "Tài liệu đầy đủ về Adapty Developer CLI"
description: "Tài liệu đầy đủ về tất cả các lệnh của Adapty Developer CLI."
---
:::link
Đang dùng trợ lý AI? Có sẵn [Adapty CLI skill](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) để giúp các LLM làm việc với CLI.
:::
Bài viết này liệt kê tất cả các lệnh Adapty CLI cùng với các đối số, cờ và giá trị được chấp nhận.
:::link
Để thiết lập xác thực và quản lý token, xem [Xác thực](developer-cli-authentication).
:::
## Cờ toàn cục \{#global-flags\}
Các cờ này có thể dùng với tất cả các lệnh.
| Cờ | Mô tả |
|---|---|
| `--json` | Xuất dưới dạng JSON thay vì văn bản định dạng |
| `--help` | Hiển thị trợ giúp lệnh |
Tất cả lệnh `list` cũng chấp nhận cờ phân trang:
| Cờ | Mặc định | Mô tả |
|---|---|---|
| `--page` | `1` | Số trang |
| `--page-size` | `20` | Số mục mỗi trang (tối đa: 100) |
## Apps \{#apps\}
Quản lý các ứng dụng trong tài khoản Adapty của bạn. Để cấu hình qua Dashboard, xem [App settings](general).
### adapty apps list \{#adapty-apps-list\}
Liệt kê tất cả ứng dụng trong tài khoản Adapty của bạn.
```bash
adapty apps list
```
Chấp nhận [cờ phân trang](#global-flags).
### adapty apps get \{#adapty-apps-get\}
Lấy thông tin chi tiết của một ứng dụng cụ thể.
```bash
adapty apps get
:::note Để theo dõi các sự kiện gói đăng ký, hãy dùng tích hợp [Webhook](webhook) trong Adapty hoặc tích hợp trực tiếp với dịch vụ hiện có của bạn. ::: ## Trường hợp 1: Đồng bộ người dùng đăng ký giữa web và mobile \{#case-1-sync-subscribers-between-web-and-mobile\} Nếu bạn dùng các nhà cung cấp thanh toán web như Stripe, ChargeBee hoặc các dịch vụ khác, bạn có thể dễ dàng đồng bộ người dùng đăng ký. Cách thực hiện: 1.
ID hồ sơ người dùng Adapty của người dùng. Hiển thị trong trường **Adapty ID** trên trang [Adapty Dashboard -> **Profiles**](https://app.adapty.io/profiles/users) -> trang hồ sơ cụ thể.
Có thể dùng thay thế cho **adapty-customer-user-id**, dùng một trong hai đều được.
| | **adapty-customer-user-id** |ID người dùng trong hệ thống của bạn. Hiển thị trong trường **Customer user ID** trên trang [Adapty Dashboard -> **Profiles**](https://app.adapty.io/profiles/users) -> trang hồ sơ cụ thể.
Có thể dùng thay thế cho **adapty-profile-id**, dùng một trong hai đều được.
⚠️ Chỉ hoạt động nếu bạn
### 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 cho họ.
- Nếu bạn **chưa 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.
:::tip
Khi tạo customer user ID, hãy lưu nó cùng với dữ liệu người dùng để bạn có thể gửi cùng một ID khi họ đăng nhập từ thiết bị mới hoặc cài đặt lại ứng dụng của bạn.
:::
Luôn `await` `identify` trước khi gọi các phương thức SDK khác. Các lời gọi đồng thời sẽ tạo ra lỗi `#3006 profileWasChanged` hoặc kết thúc trên hồ sơ người dùng ẩn danh. Xem [Thứ tự gọi trong Capacitor SDK](capacitor-sdk-call-order).
```typescript showLineNumbers
try {
await adapty.identify({ customerUserId: "YOUR_USER_ID" });
// successfully identified
} catch (error) {
// handle the error
}
```
### 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.
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 mới trống 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 customer user ID hiện có (cái bạn đã sử dụng trước đây) hoặc customer user ID mới. Nếu bạn truyền cái mới, hồ sơ người dùng mới được tạo khi kích hoạt sẽ tự động được liên kết với customer user ID đó.
:::tip
Để loại trừ các hồ sơ người dùng trống đã tạo khỏi analytics của dashboard, hãy vào **App settings** và thiết lập [**Installs definition for analytics**](general#4-installs-definition-for-analytics).
:::
```typescript showLineNumbers
await adapty.activate({
apiKey: "YOUR_PUBLIC_SDK_KEY",
params: {
customerUserId: "YOUR_USER_ID"
}
});
```
### Đăng xuất người dùng \{#log-users-out\}
Nếu bạn có nút để đăng xuất người dùng, hãy sử dụng phương thức `logout`. Thao tác này tạo một ID hồ sơ người dùng ẩn danh mới cho người dùng.
```typescript showLineNumbers
try {
await adapty.logout();
// successful logout
} catch (error) {
// handle the error
}
```
:::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ể mua hàng cả trước và sau khi đăng nhập vào ứng dụng, bạn không cần thiết lập thêm:
Cách hoạt động:
1. Khi người dùng chưa đăng nhập thực hiện mua hàng, Adapty liên kết giao dịch đó 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 của họ, 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 hiện có (customer user ID đã được liên kết với một hồ sơ người dùng), Adapty sẽ tự động đồng bộ các giao dịch của nó.
- 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 sẽ gán customer user ID cho hồ sơ người dùng hiện tại, vì vậy toàn bộ lịch sử giao dịch mua được duy trì.
---
# File: adapty-sdk-integration-skill-capacitor
---
---
title: "Tích hợp Adapty vào ứng dụng Capacitor của bạn với kỹ năng tích hợp SDK"
description: "Sử dụng kỹ năng adapty-sdk-integration để tích hợp Adapty SDK vào ứng dụng Capacitor của bạn từ đầu đến cuối với công cụ lập trình AI của bạn."
---
:::important
Kỹ năng 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-capacitor) — 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 đúng tài liệu cần thiết.
:::
---
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-capacitor
---
---
title: "Tích hợp Adapty vào ứng dụng Capacitor 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 Capacitor của bạn bằng Cursor, Context7, ChatGPT, Claude hoặc các công cụ AI khác."
---
Hướng dẫn này sẽ dẫn bạn qua từng bước tích hợp Adapty vào ứng dụng Capacitor với sự hỗ trợ của 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ỳ dòng code SDK nào. Bạn có thể thực hiện điều này bằng một LLM skill tương tác, hoặc thủ cô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](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 — kể cả khi nào cần mở Dashboard để kết nối cửa hàng.
### Cách thiết lập thủ công qua Dashboard \{#dashboard-approach\}
Nếu bạn muốn cấu hình mọi thứ thủ công, đây là những gì bạn cần trước khi viết code. LLM của bạn không thể tự tra cứu các giá trị trên dashboard — bạn sẽ phải cung cấp chúng.
1. **Kết nối cửa hàng**: Trong Adapty Dashboard, vào **App settings → General**. Kết nối cả App Store và Google Play nếu ứng dụng Capacitor của bạn hỗ trợ cả hai nền tảng. Đây là bước bắt buộc để giao dịch mua hàng hoạt động.
[Kết nối cửa hàng](integrate-payments)
2. **Sao chép Public SDK key**: Trong Adapty Dashboard, vào **App settings → General**, rồi tìm phần **API keys**. Trong code, đây là chuỗi bạn truyền vào `adapty.activate()`.
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 phân phối chúng qua paywall.
[Thêm sản phẩm](quickstart-products)
4. **Tạo một paywall và một placement**: Trong Adapty Dashboard, tạo paywall trên trang **Paywalls**, rồi gán nó vào một placement trên trang **Placements**. Trong code, placement ID là chuỗi bạn truyền vào `adapty.getPaywall()`.
[Tạo paywall](quickstart-paywalls)
5. **Thiết lập mức độ truy cập**: Trong Adapty Dashboard, cấu hình theo từng sản phẩm trên trang **Products**. Trong code, chuỗi được kiểm tra là `profile.accessLevels['premium']?.isActive`. Mức độ truy cập `premium` mặc định phù hợp với hầu hết ứ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 code.
:::tip
Khi đã có đủ năm yếu tố 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 bước này không bắt buộc để bắt đầu code, nhưng bạn sẽ cần chúng khi tích hợp trở nên hoàn chỉ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 các 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 \{#feed-adapty-docs-to-your-llm\}
### Dùng Context7 (khuyến nghị) \{#use-context7-recommended\}
[Context7](https://context7.com) là một MCP server cho phép LLM của bạn truy cập trực tiếp tài liệu Adapty mới nhất. LLM sẽ tự động lấy đúng tài liệu dựa trên câu hỏi của bạn — 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. Để cài đặt, chạy:
```
npx ctx7 setup
```
Lệnh này sẽ tự phát hiện editor của bạn và cấu hình Context7 server. Để cài đặt thủ công, xem [kho GitHub của Context7](https://github.com/upstash/context7).
Sau khi cấu hình, tham chiếu thư viện Adapty trong prompt của bạn:
```
Use the adaptyteam/adapty-docs library to look up how to install the Capacitor 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 rất 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ột để đảm bảo mọi thứ hoạt động đúng.
:::
### Dùng tài liệu dạng văn bản thuần \{#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** bên dưới tiêu đề bài viết. Ví dụ: [adapty-cursor-capacitor.md](https://adapty.io/docs/vi/adapty-cursor-capacitor.md).
Mỗi giai đoạn trong [hướng dẫn triển khai](#implementation-walkthrough) bên dưới đều có khối "Gửi cho LLM của bạn" với các link `.md` để dán vào.
Để lấy 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 sẽ dẫn bạn qua tích hợp Adapty theo đúng thứ tự triển khai. Mỗi giai đoạn bao gồm tài liệu cần gửi cho LLM, kết quả mong đợi khi hoàn thành và các lỗi thường gặp.
### Lên kế hoạch tích hợp \{#plan-your-integration\}
Trước khi bắt đầu code, hãy yêu cầu LLM phân tích dự á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 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 nào để xử lý giao dịch mua hàng — điều này ảnh hưởng đến các hướng dẫn nó cần theo:
- [**Adapty Paywall Builder**](adapty-paywall-builder): Bạn tạo paywall trong công cụ no-code của Adapty, và SDK tự động hiển thị chúng.
- [**Paywall tự tạo**](capacitor-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ý giao dịch mua.
- [**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](capacitor-quickstart-paywalls).
### Cài đặt và cấu hình SDK \{#install-and-configure-the-sdk\}
Thêm dependency Adapty SDK bằng npm 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 phụ thuộc vào bước này.
**Hướng dẫn:** [Cài đặt & cấu hình Adapty SDK](sdk-installation-capacitor)
Gửi cho LLM của bạn:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/vi/sdk-installation-capacitor.md
```
:::tip[Checkpoint]
- **Kết quả mong đợi:** Ứng dụng build và chạy được trên cả iOS và Android. Console hiển thị log kích hoạt Adapty.
- **Lỗi thường gặp:** "Public API key is missing" → kiểm tra lại xem bạn đã thay placeholder bằng key thật từ App settings chưa.
:::
### Hiển thị paywall và xử lý giao dịch mua \{#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. Các hướng dẫn bạn cần phụ thuộc vào cách bạn xử lý giao dịch mua.
Hãy test từng giao dịch trong sandbox ngay khi triển khai xong — đừng để đến cuối. Xem [Test giao dịch trong sandbox](test-purchases-in-sandbox) để biết hướng dẫn cài đặt.
tùy chọn
mặc định: `en`
|Định danh của [bản địa hóa paywall](add-paywall-locale-in-adapty-paywall-builder). Tham số này phải là mã ngôn ngữ gồm một hoặc hai thẻ phụ được phân tách bởi dấu trừ (**-**). Thẻ phụ đầu tiên là ngôn ngữ, thẻ phụ 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ã 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.
| | **params** | tùy chọn | Tham số bổ sung để lấy paywall. | **Đừng hardcode ID sản phẩm.** ID duy nhất bạn nên hardcode là placement ID. Paywall được cấu hình từ xa, vì vậy số lượng sản phẩm và ưu đãi có thể thay đổi bất kỳ lúc nào. Ứng dụng của bạn phải xử lý những thay đổi này một cách linh hoạt — nếu hôm nay paywall trả về hai sản phẩm và ngày mai trả về ba, hãy hiển thị tất cả chúng mà không cần thay đổi code. Tham số phản hồi: | Tham số | Mô tả | | :-------- |:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Một đối tượng [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall) chứa 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 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 chưa đượ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ó chứa `ViewConfiguration` không — điều này cho biết paywall đượ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ư một Paywall Builder paywall; nếu không, [xử lý nó như một Remote Config paywall](present-remote-config-paywalls-capacitor). Trong Capacitor 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 dùng lại, hãy gọi phương thức `createPaywallView` một lần nữa. ::: ```typescript showLineNumbers if (paywall.hasViewConfiguration) { try { const view = await createPaywallView(paywall); } catch (error) { // handle the error } } else { // use your custom logic } ``` 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. | | **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ể để tạo nội dung cá nhân hóa trong paywall. Tham khảo chủ đề Custom tags in paywall builder để biết thêm chi tiết. | | **prefetchProducts** | tùy chọn | Bật để tối ưu hóa thời điểm hiển thị sản phẩm trên màn hình. Khi là `true`, AdaptyUI sẽ tự động lấy các sản phẩm cần thiết. Mặc định: `false`. | :::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](capacitor-localizations-and-locale-codes). ::: Sau khi có view, [hiển thị paywall](capacitor-present-paywalls). ## Lấy paywall cho đối tượng mặc định để tải nhanh hơn \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Thông thường, paywall được tải 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 trường hợp bạn có nhiều đối tượng và paywall, và người dùng có kết nối internet yếu, việc tải paywall có thể mất nhiều thời gian hơn mong đợi. 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. Để 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ư đã mô tả chi tiết trong phần [Lấy thông tin Paywall](#fetch-paywall-designed-with-paywall-builder) ở trên. :::warning Tại sao chúng tôi khuyến nghị dùng `getPaywall` Phương thức `getPaywallForDefaultAudience` có một số nhược điểm đáng kể: - **Các vấn đề tương thích ngược tiềm ẩn**: Nếu bạn cần hiển thị các paywall khác nhau cho các phiên bản ứng dụng khác nhau (hiện tại và tương lai), bạn sẽ phải thiết kế paywall hỗ trợ phiên bản hiện tại (cũ) hoặc chấp nhận rằng người dùng với phiên bản hiện tại (cũ) có thể gặp sự cố với paywall không render được. - **Mất khả năng nhắm mục tiêu**: Tất cả người dùng sẽ thấy cùng một paywall được thiết kế cho đối tượng **All Users**, nghĩa là bạn mất khả năng nhắm mục tiêu cá nhân hóa (bao gồm theo quốc gia, attribution marketing hoặc các thuộc tính tùy chỉnh của bạn). Nếu bạn sẵn sàng chấp nhận những nhược điểm này để hưởng lợi từ việc tải paywall nhanh hơn, hãy sử dụng phương thức `getPaywallForDefaultAudience` như sau. Nếu không, hãy tiếp tục dùng `getPaywall` như đã mô tả [ở trên](#fetch-paywall-designed-with-paywall-builder). ::: ```typescript showLineNumbers try { const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', }); // the requested paywall } catch (error) { // handle the error } ``` :::note Phương thức `getPaywallForDefaultAudience` khả dụng từ Capacitor SDK phiên bản 2.11.2 trở lên. ::: | 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 của bạn. | | **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ẻ phụ được phân tách bởi dấu trừ (**-**). Thẻ phụ đầu tiên là ngôn ngữ, thẻ phụ 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ã locale](capacitor-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.
| | **params** | tùy chọn | Tham số bổ sung để lấy paywall. | ## 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 và video hero có ID được định sẵn: `hero_image` và `hero_video`. Trong một custom asset bundle, bạn nhắm mục tiêu 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 nhau cho một số người dùng. - Hiển thị hình ảnh xem trước cục bộ trong khi hình ảnh chính từ xa đang tải. - Hiển thị hình ả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 Capacitor 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: ```typescript showLineNumbers const customAssets: Recordtù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 subtag được phân tách bằng dấu trừ (**-**). Subtag đầu tiên là ngôn ngữ, subtag thứ hai là khu vực.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha Brazil.
Xem [Localizations and locale codes](capacitor-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.
| | **params.fetchPolicy** |tùy chọn
mặc định: `'reload_revalidating_cache_data'`
|Theo mặc định, SDK sẽ cố tải dữ liệu từ server 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 luôn nhận được dữ liệu mới nhất.
Tuy nhiên, nếu người dùng của bạn thường xuyên gặp kết nối internet không ổn định, hãy cân nhắc dùng `'return_cache_data_else_load'` để 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 sẽ tải nhanh hơn dù kết nối có yếu đến đâu. Cache được cập nhật thường xuyên, nên hoàn toàn an toàn khi dùng trong phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn tồn tại khi khởi động lại ứng dụng và chỉ bị xóa khi cài đặt lại ứng dụng hoặc xóa thủ công.
| | **params.loadTimeoutMs** |tùy chọn
mặc định: 5000 ms
|Giá trị này giới hạn thời gian chờ (tính bằng mili giây) 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 `loadTimeoutMs`, vì quá trình có thể bao gồm nhiều request khác nhau bên dưới.
| **Đừng hardcode ID sản phẩm.** ID duy nhất bạn nên hardcode là placement ID. Paywalls được cấu hình từ xa, vì vậy số lượng sản phẩm và ưu đãi có thể thay đổi bất kỳ lúc nào. Ứng dụng của bạn phải xử lý những thay đổi này một cách linh hoạt — nếu paywall hôm nay trả về hai sản phẩm và ngày mai trả về ba, hãy hiển thị tất cả mà không cần thay đổi code. Tham số trả về: | Tham số | Mô tả | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Đối tượng [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall) bao 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ó: ```typescript showLineNumbers try { const products = await adapty.getPaywallProducts({ paywall }); // the requested products list } catch (error) { console.error('Failed to fetch products:', error); } ``` Tham số trả về: | Tham số | Mô tả | | :-------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Danh sách đối tượng [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) bao gồm: định danh sản phẩm, tên sản phẩm, giá, tiền tệ, thời hạn gói đăng ký và một số thuộc tính khác. | Khi tự thiết kế giao diện paywall, bạn sẽ cần truy cập các thuộc tính từ đối tượng [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct). Dưới đây là các thuộc tính được dùng phổ biến nhất, nhưng hãy tham khảo tài liệu được liên kết để xem đầy đủ tất cả các thuộc tính có sẵn. | Thuộc tính | Mô tả | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Để hiển thị tiêu đề sản phẩm, 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 locale của thiết bị. | | **Price** | Để hiển thị giá đã được bản địa hóa, dùng `product.price?.localizedString`. Bản địa hóa này dựa trên thông tin locale 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, dùng `product.price?.currencySymbol`. | | **Subscription Period** | Để hiển thị chu kỳ (ví dụ: tuần, tháng, năm, v.v.), dùng `product.subscription?.localizedSubscriptionPeriod`. Bản địa hóa này dựa trên locale của thiết bị. Để lấy chu kỳ gói đăng ký theo dạng lập trình, dùng `product.subscription?.subscriptionPeriod`. Từ đó bạn có thể truy cập thuộc tính `unit` để lấy độ dài (tức là 'day', 'week', 'month', 'year', hoặc 'unknown'). Giá trị `numberOfUnits` sẽ cho bạn số đơn vị chu kỳ. Ví dụ, với gói đăng ký theo quý, bạn sẽ thấy `'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ỉ số khác cho biết 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: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 subtag được phân tách bằng dấu trừ (**-**). Subtag đầu tiên là ngôn ngữ, subtag thứ hai là khu vực.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha Brazil.
Xem [Localizations and locale codes](capacitor-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.
| | **params.fetchPolicy** |tùy chọn
mặc định: `'reload_revalidating_cache_data'`
|Theo mặc định, SDK sẽ cố tải dữ liệu từ server 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 luôn nhận được dữ liệu mới nhất.
Tuy nhiên, nếu người dùng của bạn thường xuyên gặp kết nối internet không ổn định, hãy cân nhắc dùng `'return_cache_data_else_load'` để 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 sẽ tải nhanh hơn dù kết nối có yếu đến đâu. Cache được cập nhật thường xuyên, nên hoàn toàn an toàn khi dùng trong phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn tồn tại khi khởi động lại ứng dụng và chỉ bị xóa khi cài đặt lại ứng dụng hoặc xóa thủ công.
| --- # File: present-remote-config-paywalls-capacitor --- --- title: "Hiển thị paywall được thiết kế bằng Remote Config trong Capacitor SDK" description: "Khám phá cách trình bày paywall Remote Config trong Adapty Capacitor 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 có thể thấy. Vì Remote Config mang lại sự linh hoạt theo nhu cầu của bạn, bạn hoàn toàn kiểm soát những gì được hiển thị và giao diện paywall trông như thế nào. Chúng tôi cung cấp một phương thức để lấy cấu hình remote, giúp bạn tự do trình bày paywall tùy chỉnh đã được thiết lập 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à trích xuất các giá trị cần thiết. ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); const headerText = paywall.remoteConfig?.data?.['header_text']; } catch (error) { console.error('Failed to fetch paywall:', error); } ``` 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 thành một trang trực quan hấp dẫn. Hãy đảm bảo thiết kế tương thích với nhiều loại màn hình điện thoại và hướng xoay khác nhau, 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-capacitor#track-paywall-view-events) như mô tả bên dưới, để Adapty analytics có thể thu thập dữ liệu 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 luồng 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](capacitor-making-purchases). Chúng tôi khuyến nghị [tạo một paywall dự phòng](capacitor-use-fallback-paywalls). Paywall dự phòng 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 đó. ## Theo dõ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 dữ liệu mua hàng được thu thập tự độ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)`, và nó sẽ được phản ánh trong các chỉ số 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). ::: ```typescript showLineNumbers try { await adapty.logShowPaywall({ paywall }); } catch (error) { console.error('Failed to log paywall view:', error); } ``` Tham số yêu cầu: | Tham số | Bắt buộc | Mô tả | | :---------- | :------- | :--------------------------------------------------------- | | **paywall** | bắt buộc | Một đối tượng [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall). | --- # File: capacitor-making-purchases --- --- title: "Thực hiện mua hàng trong ứng dụng với Capacitor 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 không thể thiếu để 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ỉ cần hiển thị paywall là đủ để hỗ trợ mua hàng nếu bạn dùng [Paywall Builder](adapty-paywall-builder) để tùy chỉnh paywall của mình. Nếu bạn không dùng Paywall Builder, bạn phải 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 đóng vai trò là cổng để người dùng tương tác với paywall và thực hiện giao dịch. Nếu paywall của bạn có ưu đãi đang hoạt động cho sản phẩm mà người dùng muốn mua, Adapty sẽ tự động áp dụng ưu đãi đó tại thời điểm mua hàng. Hãy đảm bảo bạn đã [hoàn tất cấu hình ban đầu](quickstart) mà không bỏ qua bước nào. Nếu thiếu, chúng tôi không thể xác thực giao dịch mua hàng. ## Thực hiện mua hàng \{#make-purchase\} :::note **Đang dùng [Paywall Builder](adapty-paywall-builder)?** Giao dịch mua hàng được xử lý tự động — bạn có thể bỏ qua bước này. **Muốn có hướng dẫn từng bước?** Xem [hướng dẫn quickstart](capacitor-implement-paywalls-manually) để có hướng dẫn triển khai đầy đủ từ đầu đến cuối. ::: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product }); if (result.type === 'success') { const isSubscribed = result.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features console.log('User is now subscribed!'); } } else if (result.type === 'user_cancelled') { console.log('Purchase cancelled by user'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { console.error('Purchase failed:', error); } ``` Tham số yêu cầu: | Tham số | Bắt buộc | Mô tả | | :---------- | :------- |:----------------------------------------------------------------------------------------------------------------------------| | **product** | bắt buộc | Đối tượng [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) lấy từ paywall. | Tham số phản hồi: | Tham số | Mô tả | |---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **result** | Đối tượng [`AdaptyPurchaseResult`](https://capacitor.adapty.io/types/adaptypurchaseresult) với trường `type` cho biết kết quả mua hàng (`'success'`, `'user_cancelled'` hoặc `'pending'`) và trường `profile` chứa [`AdaptyProfile`](https://capacitor.adapty.io/interfaces/adaptyprofile) đã được cập nhật khi mua hàng thành công. | ## 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 thức hoạt động phụ thuộc vào cửa hàng: - Với App Store, gói đăng ký được cập nhật tự động 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 đang có gói đăng ký từ nhóm khác, cả hai sẽ cùng hoạt động. - Với Google Play, gói đăng ký không được cập nhật tự động. Bạn cần tự xử lý việc chuyển đổi trong code ứng dụng của mình như mô tả bên dưới. Để thay thế gói đăng ký bằng gói khác trên Android, hãy gọi phương thức `.makePurchase()` với tham số bổ sung: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product, params: { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } } }); if (result.type === 'success') { const isSubscribed = result.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features console.log('Subscription updated successfully!'); } } else if (result.type === 'user_cancelled') { console.log('Purchase cancelled by user'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { console.error('Purchase failed:', error); } ``` Tham số yêu cầu bổ sung: | Tham số | Bắt buộc | Mô tả | | :--------- | :------- | :----------------------------------------------------------- | | **params** | tùy chọn | Đối tượng kiểu [`MakePurchaseParamsInput`](https://capacitor.adapty.io/types/makepurchaseparamsinput) chứa các tham số mua hàng theo từng nền tảng. | Cấu trúc `MakePurchaseParamsInput` bao gồm: ```typescript { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } } ``` 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: - [Giới thiệu 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ỉ khả dụng khi 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ỉ diễn ra khi chu kỳ thanh toán hiện tại kết thúc. ### Quản lý gói trả trước (Android) \{#manage-prepaid-plans-android\} Nếu người dùng ứng dụng của bạn có thể mua [gói trả trước](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (ví dụ: mua gói đăng ký không tự gia hạn cho vài tháng), bạn có thể bật [giao dịch đang chờ xử lý](https://developer.android.com/google/play/billing/subscriptions#pending) cho các gói trả trước. ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { android: { enablePendingPrepaidPlans: true, }, } }); ``` ## Đổi mã ưu đãi trên iOS \{#redeem-offer-codes-in-ios\} --- no_index: true --- import Callout from '../../../components/Callout.astro';tùy chọn
mặc định: `en`
|Định danh của bản địa hóa onboarding. Tham số này được kỳ vọng là một mã ngôn ngữ gồm một hoặc hai subtag phân cách bằng dấu trừ (**-**). Subtag đầu tiên là cho ngôn ngữ, subtag 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](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.
| | **params.fetchPolicy** |tùy chọn
mặc định: `'reload_revalidating_cache_data'`
|Theo mặc định, SDK sẽ cố tải dữ liệu từ server 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 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 thường gặp kết nối internet không ổn định, hãy cân nhắc sử dụng `'return_cache_data_else_load'` để 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ọ không ổn định đến đâu. Cache được cập nhật thường xuyên, vì vậy an toàn khi sử dụng nó trong phiên để tránh các yêu cầu mạng.
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.
| | **params.loadTimeoutMs** |tùy chọn
mặc định: 5000 ms
|Giá trị này giới hạn thời gian chờ (tính bằng mili giây) 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ị chỉ định trong `loadTimeoutMs`, vì thao tác có thể bao gồm nhiều yêu cầu khác nhau bên dưới.
| Các tham số phản hồi: | Tham số | Mô tả | |:----------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onboarding** | Một đối tượng [`AdaptyOnboarding`](https://capacitor.adapty.io/interfaces/adaptyonboarding) bao gồm: định danh và cấu hình onboarding, Remote Config, và một số thuộc tính khác. | ## Tăng tốc lấy onboarding bằng onboarding dành cho đố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 các 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 vấn đề 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ể 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 đúng. - **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 theo quốc gia, attribution, hoặc các thuộc tính tùy chỉnh. Nếu việc lấy nhanh hơn có lợi hơn những hạn chế 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 sử dụng `getOnboarding` như mô tả [ở trên](#fetch-onboarding). ::: ```typescript showLineNumbers try { const onboarding = await adapty.getOnboardingForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data' // Load from server, fallback to cache } }); console.log('Default audience onboarding fetched successfully'); } catch (error) { console.error('Failed to fetch default audience onboarding:', error); } ``` Các 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 onboarding. Tham số này được kỳ vọng là một mã ngôn ngữ gồm một hoặc hai subtag phân cách bằng dấu trừ (**-**). Subtag đầu tiên là cho ngôn ngữ, subtag 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](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.
| | **params.fetchPolicy** |tùy chọn
mặc định: `'reload_revalidating_cache_data'`
|Theo mặc định, SDK sẽ cố tải dữ liệu từ server 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 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 thường gặp kết nối internet không ổn định, hãy cân nhắc sử dụng `'return_cache_data_else_load'` để 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ọ không ổn định đến đâu. Cache được cập nhật thường xuyên, vì vậy an toàn khi sử dụng nó trong phiên để tránh các yêu cầu mạng.
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.
| --- # File: capacitor-present-onboardings --- --- title: "Hiển thị onboarding trong Capacitor SDK" description: "Khám phá cách hiển thị onboarding trên Capacitor để tăng tỷ lệ chuyển đổi và doanh thu." --- Nếu bạn đã tùy chỉnh một onboarding bằng builder, bạn không cần lo lắng về việc render nó trong code ứng dụng di động để hiển thị cho người dùng. Onboarding đó đã bao gồm cả nội dung cần hiển thị lẫn cách hiển thị. Trước khi bắt đầu, hãy đảm bảo rằng: 1. Bạn đã [tạo một onboarding](create-onboarding). 2. Bạn đã thêm onboarding vào một [placement](placements). ## Hiển thị onboarding \{#present-onboarding\} Để hiển thị một 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 onboarding, hãy gọi `createOnboardingView` thêm một lần nữa để tạo một 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. ::: ```typescript showLineNumbers try { const view = await createOnboardingView(onboarding); view.setEventHandlers({ onClose: (actionId, meta) => { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, onCustom: (actionId, meta) => { console.log('Custom action:', actionId); return false; // Don't close the onboarding } }); await view.present(); console.log('Onboarding presented successfully'); } catch (error) { console.error('Failed to present onboarding:', error); } ``` ## Cấu hình kiểu trình bày 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ị `'full_screen'` (mặc định) hoặc `'page_sheet'`. ```typescript showLineNumbers await view.present({ iosPresentationStyle: 'page_sheet' }); ``` ## Tùy chỉnh cách mở liên kết trong onboarding \{#customize-how-links-open-in-onboardings\} :::important 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ở trong trình duyệt trong ứng dụng. Điều này mang lại trải nghiệm liền mạch cho người dùng bằng cách hiển thị các trang web ngay trong ứng dụng của bạn, cho phép người dùng xem mà không cần chuyển sang ứng dụng khác. Nếu bạn muốn mở liên kết trong trình duyệt ngoài thay thế, bạn có thể tùy chỉnh hành vi này bằng cách đặt tham số `openIn` thành `browser_out_app`: ```typescript showLineNumbers await view.present({ openIn: 'browser_out_app' }); // default — browser_in_app ``` ## Các bước tiếp theo \{#next-steps\} Sau khi hiển thị onboarding, bạn sẽ muốn [xử lý các tương tác và sự kiện của người dùng](capacitor-handling-onboarding-events). Tìm hiểu cách xử lý các sự kiện onboarding để phản hồi hành động của người dùng và theo dõi analytics. --- # File: capacitor-handling-onboarding-events --- --- title: "Xử lý sự kiện onboarding trong Capacitor SDK" description: "Xử lý các sự kiện liên quan đến onboarding trong Capacitor bằng Adapty." --- 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ể xử lý. Sử dụng phương thức `setEventHandlers` để xử lý các sự kiện này khi hiển thị màn hình độc lập. Trước khi bắt đầu, hãy đảm bảo rằng: 1. Bạn đã [tạo một onboarding](create-onboarding). 2. Bạn đã thêm onboarding vào một [placement](placements). ## Thiết lập event handler \{#set-up-event-handlers\} Để xử lý sự kiện cho các onboarding, hãy sử dụng phương thức `view.setEventHandlers`: ```typescript showLineNumbers try { const view = await createOnboardingView(onboarding); view.setEventHandlers({ onAnalytics(event, meta) { console.log('Analytics event:', event); }, onClose(actionId, meta) { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, onCustom(actionId, meta) { console.log('Custom action:', actionId); return false; // Don't close the onboarding }, onPaywall(actionId, meta) { console.log('Paywall action:', actionId); view.dismiss().then(() => { openPaywall(actionId); }); }, onStateUpdated(action, meta) { console.log('State updated:', action); }, onFinishedLoading(meta) { console.log('Onboarding finished loading'); }, onError(error) { console.error('Onboarding error:', error); }, }); await view.present(); } catch (error) { console.error('Failed to present onboarding:', error); } ``` ## Các loại sự kiện \{#event-types\} Các phần dưới đây mô tả các loại sự kiện khác nhau mà bạn có thể xử lý. ### Xử lý hành động tùy chỉnh \{#handle-custom-actions\} Trong builder, bạn có thể thêm hành động **custom** vào 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**, event handler sẽ được kích hoạt với tham số `actionId` trùng với **Action ID** trong builder. Bạn có thể tự tạo ID, ví dụ "allowNotifications".
```typescript showLineNumbers
view.setEventHandlers({
onCustom(actionId, meta) {
switch (actionId) {
case 'login':
console.log('Login action triggered');
break;
case 'allow_notifications':
console.log('Allow notifications action triggered');
break;
}
return false; // Don't close the onboarding
},
});
```
:::important
Lưu ý rằng bạn cần tự quản lý những gì xảy ra khi người dùng đóng onboarding. Ví dụ, bạn cần dừng việc hiển thị onboarding đó.
:::
```typescript showLineNumbers
view.setEventHandlers({
onClose(actionId, meta) {
console.log('Onboarding closed:', actionId);
return true; // Allow the onboarding to close
},
});
```
This error code indicates that the user canceled a payment request.
No action is required, but in terms of the business logic, you can offer a discount to your user or remind them later.
| | paymentInvalid | 3 | This error indicates that one of the payment parameters was not recognized by the store. | | paymentNotAllowed | 4 |This error code indicates that the user is not allowed to authorize payments. Possible reasons:
- Payments are not supported in the user's country.
- The user is a minor.
| | storeProductNotAvailable | 5 | This error code indicates that the requested product is absent from the App Store. Make sure the product is available for the used country. | | cloudServicePermissionDenied | 6 | This error code indicates that the user has not allowed access to Cloud service information. | | cloudServiceNetworkConnectionFailed | 7 | This error code indicates that the device could not connect to the network. | | cloudServiceRevoked | 8 | This error code indicates that the user has revoked permission to use this cloud service. | | privacyAcknowledgementRequired | 9 | This error code indicates that the user has not yet acknowledged the store privacy policy. | | unauthorizedRequestData | 10 | This error code indicates that the request is built incorrectly. | | invalidOfferIdentifier | 11 |The offer identifier is not valid. Possible reasons:
- You have not set up an offer with that identifier in the App Store.
- You have revoked the offer.
- You misprinted the offer ID.
| | invalidSignature | 12 | This error code indicates that the signature in a payment discount is not valid. Make sure you've filled out the **In-app purchase Key ID** field and uploaded the **In-App Purchase Private Key** file. Refer to the [Configure App Store integration](app-store-connection-configuration) topic for details. | | missingOfferParams | 13 |This error indicates issues with Adapty integration or with offers.
Refer to the [Configure App Store integration](app-store-connection-configuration) and to [Offers](offers) for details on how to set them up.
| | invalidOfferPrice | 14 | This error code indicates that the price you specified in the store is no longer valid. Offers must always represent a discounted price. | ## Custom Android codes | Error | Code | Description | |-----|----|-----------| | adaptyNotInitialized | 20 | You need to properly configure Adapty SDK by `Adapty.activate` method. Learn how to do it [for React Native]( sdk-installation-reactnative). | | productNotFound | 22 | This error indicates that the product requested for purchase is not available in the store. | | invalidJson | 23 | The paywall JSON is not valid. Fix it in the Adapty Dashboard. Refer to the [Customize paywall with remote config](customize-paywall-with-remote-config) topic for details on how to fix it. | | currentSubscriptionToUpdateNotFoundInHistory | 24 | The original subscription that needs to be renewed is not found. | | pendingPurchase | 25 | This error indicates that the purchase state is pending rather than purchased. Refer to the [Handling pending transactions](https://developer.android.com/google/play/billing/integrate#pending) page in the Android Developer docs for details. | | billingServiceTimeout | 97 | This error indicates that the request has reached the maximum timeout before Google Play can respond. This could be caused, for example, by a delay in the execution of the action requested by the Play Billing Library call. | | featureNotSupported | 98 | The requested feature is not supported by the Play Store on the current device. | | billingServiceDisconnected | 99 | This fatal error indicates that the client app’s connection to the Google Play Store service via the `BillingClient` has been severed. | | billingServiceUnavailable | 102 | This transient error indicates the Google Play Billing service is currently unavailable. In most cases, this means there is a network connection issue anywhere between the client device and Google Play Billing services. | | billingUnavailable | 103 |This error indicates that a user billing error occurred during the purchase process. Examples of when this can occur include:
1\. The Play Store app on the user's device is out of date.
2. The user is in an unsupported country.
3. The user is an enterprise user, and their enterprise admin has disabled users from making purchases.
4. Google Play is unable to charge the user’s payment method. For example, the user's credit card might have expired.
5. The user is not logged into the Play Store app.
| | developerError | 105 | This is a fatal error that indicates you're improperly using an API. | | billingError | 106 | This is a fatal error that indicates an internal problem with Google Play itself. | | itemAlreadyOwned | 107 | The consumable product has already been purchased. | | itemNotOwned | 108 | This error indicates that the requested action on the item failed sin | ## Custom StoreKit codes | Error | Code | Description | |-----|----|-----------| | noProductIDsFound | 1000 |This error indicates that none of the products in the paywall is available in the store.
If you are encountering this error, please follow the steps below to resolve it:
1. Check if all the products have been added to Adapty Dashboard.
2. Ensure that the Bundle ID of your app matches the one from the Apple Connect.
3. Verify that the product identifiers from the app stores match with the ones you have added to the Dashboard. Please note that the identifiers should not contain Bundle ID, unless it is already included in the store.
4. Confirm that the app paid status is active in your Apple tax settings. Ensure that your tax information is up-to-date and your certificates are valid.
5. Check if a bank account is attached to the app, so it can be eligible for monetization.
6. Check if the products are available in all regions.Also, ensure that your products are in **“Ready to Submit”** state.
| | productRequestFailed | 1002 |Unable to fetch available products at the moment. Possible reason:
- No cache was yet created and no internet connection at the same time.
| | cantMakePayments | 1003 | In-App purchases are not allowed on this device. | | noPurchasesToRestore | 1004 | This error indicates that Google Play did not find the purchase to restore. | | cantReadReceipt | 1005 |There is no valid receipt available on the device. This can be an issue during sandbox testing.
No action is required, but in terms of the business logic, you can offer a discount to your user or remind them later.
| | productPurchaseFailed | 1006 | Product purchase failed. This wraps an underlying StoreKit error — read the wrapped error (or enable verbose logs to see it in the console) for the actual reason. The wrapped error is typically one of the StoreKit codes 0–14 in the table above — most commonly `paymentCancelled`, `paymentInvalid`, `paymentNotAllowed`, or `invalidOfferPrice`. If you can't identify a specific reason, try a new [sandbox profile](test-purchases-in-sandbox); if it still fails, contact Apple support. | | refreshReceiptFailed | 1010 | This error indicates that the receipt was not received. Applicable to StoreKit 1 only. | | receiveRestoredTransactionsFailed | 1011 | Purchase restoration failed. | ## Custom network codes | Error | Code | Description | | :------------------- | :--- | :----------------------------------------------------------- | | notActivated | 2002 | You need to properly configure Adapty SDK by `Adapty.activate` method. Learn how to do it [for React Native](sdk-installation-reactnative). | | badRequest | 2003 | Bad request. | | serverError | 2004 | Server error. | | networkFailed | 2005 | The network request failed. | | decodingFailed | 2006 | This error indicates that response decoding failed. | | encodingFailed | 2009 | This error indicates that request encoding failed. | | analyticsDisabled | 3000 | We can't handle analytics events, since you've opted it out. Refer to the [Analytics integration](analytics-integration) topic for details. | | wrongParam | 3001 | This error indicates that some of your parameters are not correct: blank when it cannot be blank or wrong type, etc. | | activateOnceError | 3005 | It is not possible to call `.activate` method more than once. | | profileWasChanged | 3006 | The user profile was changed during the operation. | | fetchTimeoutError | 3101 | This error means that the paywall could not be fetched within the set limit. To avoid this situation, [set up local fallbacks](fetch-paywalls-and-products). | | operationInterrupted | 9000 | This operation was interrupted by the system. | --- # File: capacitor-sdk-migration-guides --- --- title: "Capacitor SDK Migration Guides" description: "Migration guides for Adapty Capacitor SDK versions." --- This page contains all migration guides for Adapty Capacitor SDK. Choose the version you want to migrate to for detailed instructions: - [**Migrate to v3.16**](migration-to-capacitor-316) --- # File: migration-to-capacitor-316 --- --- title: "Migrate Adapty Capacitor SDK to v3.16" description: "Migrate sang Adapty Capacitor SDK v3.16 để có hiệu suất tốt hơn và các tính năng kiếm tiền mới." --- Bắt đầu từ Adapty SDK v3.16.0, Capacitor 8 là bắt buộc. Nếu bạn cần Capacitor 7, hãy dùng Adapty SDK v3.15. Để nâng cấp lên Capacitor SDK v3.16, hãy đảm bảo dự án của bạn đang sử dụng Capacitor 8. Nếu bạn vẫn đang dùng Capacitor 7, bạn có hai lựa chọn: 1. **Nâng cấp lên Capacitor 8**: Làm theo [hướng dẫn migration chính thức của Capacitor](https://capacitorjs.com/docs/updating/8-0) để cập nhật dự án, sau đó cài đặt Adapty SDK v3.16. 2. **Tiếp tục dùng Adapty SDK v3.15**: Nếu việc nâng cấp lên Capacitor 8 chưa khả thi, hãy tiếp tục sử dụng Adapty SDK v3.15, phiên bản này hỗ trợ Capacitor 7. --- # End of Documentation _Generated on: 2026-06-24T14:36:38.682Z_ _Successfully processed: 43/43 files_ # FLUTTER - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: vi Generated on: 2026-06-24T14:36:38.685Z Total files: 41 --- # File: sdk-installation-flutter --- --- title: "Cài đặt & cấu hình Flutter SDK" description: "Hướng dẫn từng bước cài đặt Adapty SDK trên Flutter cho các ứng dụng dựa trên gói đăng ký." --- Adapty SDK gồm hai module chính để tích hợp liền mạch vào ứng dụng Flutter 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 để tạo paywall đa nền tảng một cách 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-Flutter/tree/master/example) của chúng tôi, minh họa toàn bộ quá trình cài đặt, 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+, nhưng cần iOS 15.0+ để hoạt động đúng với các paywall được tạo trong Paywall Builder. :::info Adapty tương thích với Google Play Billing Library đến phiên bản 8.x. Mặc định, Adapty hoạt động với Google Play Billing Library v7.0.0, nhưng nếu bạn muốn sử dụng phiên bản mới hơn, bạn có thể thêm thủ công [dependency](https://developer.android.com/google/play/billing/integrate#dependency). ::: --- no_index: true --- import Callout from '../../../components/Callout.astro';
### 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 hoặc đăng ký), hãy sử dụng phương thức `identify` để thiết lập customer user ID của họ.
- Nếu bạn **chưa sử dụng customer user ID này trước đây**, Adapty sẽ tự động liên kết nó với hồ sơ 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ơ đượ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 người.
:::
Luôn `await` `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 sẽ trỏ đến hồ sơ ẩn danh. Xem [Thứ tự gọi trong Flutter SDK](flutter-sdk-call-order).
```dart showLineNumbers
try {
await Adapty().identify(customerUserId); // Unique for each user
} on AdaptyError catch (adaptyError) {
// handle the error
} catch (e) {
}
```
### 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.
Nếu bạn biết customer user ID nhưng chỉ thiết lập 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ơ ẩn danh mới và chỉ chuyển sang hồ sơ hiện có sau khi bạn gọi `identify`.
Bạn có thể truyền một customer user ID hiện có (cái bạn đã sử dụng trước đây) hoặc một cái mới. Nếu bạn truyền một cái mới, hồ sơ mới được tạo khi kích hoạt sẽ tự động được liên kết với customer user ID đó.
:::note
Theo mặc định, việc tạo hồ sơ ẩn danh không ảnh hưởng đến các dashboard analytics, vì số lần cài đặt được tính dựa trên device ID.
Một device ID đại diện cho một lần cài đặt ứng dụng từ cửa hàng trên 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ơ (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 ra thêm sự kiện cài đặt.
Nếu bạn muốn đếm số lần 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).
:::
```dart showLineNumbers"
try {
await Adapty().activate(
configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY')
..withCustomerUserId(YOUR_CUSTOMER_USER_ID) // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one.
);
} catch (e) {
// handle the error
}
```
### Đăng xuất người dùng \{#log-users-out\}
Nếu bạn có nút để đăng xuất người dùng, hãy sử dụng phương thức `logout`.
:::important
Đăng xuất người dùng sẽ tạo một hồ sơ ẩn danh mới cho người dùng đó.
:::
```dart showLineNumbers
try {
await Adapty().logout();
} on AdaptyError catch (adaptyError) {
// handle the error
} catch (e) {
// handle unknown error
}
```
:::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ẽ vẫn 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 liên kết nó với ID hồ sơ ẩn danh của họ.
2. Khi người dùng đăng nhập vào tài khoản của họ, Adapty chuyển sang làm việc với hồ sơ đã 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 sẽ gán customer user ID cho hồ sơ 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ơ), bạn cần lấy mức độ truy cập thực tế sau khi chuyển hồ sơ. Bạn có thể gọi [`getProfile`](flutter-check-subscription-status) ngay sau khi xác định, hoặc [lắng nghe các cập nhật hồ sơ](flutter-check-subscription-status) để dữ liệu tự động đồng bộ.
## Các bước tiếp theo \{#next-steps\}
Chúc mừng! Bạn đã triển khai logic thanh toán trong ứng dụng! Chúc bạn thành công với việc kiếm tiền từ ứng dụng!
Để tận dụng Adapty nhiều 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
- [**Onboarding**](flutter-onboardings): Thu hút người dùng bằng onboarding và tăng tỷ lệ giữ chân
- [**Tích hợp**](configuration): Tích hợp với các dịch vụ attribution marketing và analytics chỉ trong một dòng code
- [**Thiết lập thuộc tính hồ sơ tùy chỉnh**](flutter-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, giúp bạn có thể chạy A/B test hoặc hiển thị các paywall khác nhau cho các nhóm người dùng khác nhau
---
# File: adapty-sdk-integration-skill-flutter
---
---
title: "Tích hợp Adapty vào ứng dụng Flutter với kỹ năng tích hợp SDK"
description: "Sử dụng kỹ năng adapty-sdk-integration để tích hợp Adapty SDK vào ứng dụng Flutter của bạn từ đầu đến cuối với công cụ lập trình AI."
---
:::important
Kỹ năng 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-flutter) — 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-flutter
---
---
title: "Tích hợp Adapty vào ứng dụng Flutter 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 Flutter của bạn bằng Cursor, Context7, ChatGPT, Claude hoặc các công cụ AI khác."
---
Hướng dẫn này sẽ giúp bạn tích hợp Adapty vào ứng dụng Flutter từng bước với công cụ lập trình AI — bạn cung cấp cho nó đú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ỳ code SDK nào. Bạn có thể thực hiện điều này bằng 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 app, sản phẩm, mức độ truy cập, paywall và placement trực tiếp — không cần mở Dashboard cho từng bước. Bạn chỉ cần [kết nối các cửa hàng](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ả lúc cần mở Dashboard để kết nối các cửa hàng.
### Cách thiết lập thủ công trên Dashboard \{#dashboard-approach\}
Nếu bạn muốn tự cấu hình mọi thứ, đây là những gì cần có trước khi viết code. LLM của bạn không thể tra cứu các giá trị trên dashboard — bạn sẽ cần tự cung cấp chúng.
1. **Kết nối các 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 Flutter của bạn hỗ trợ cả hai nền tảng. Đây là điều bắt buộc để các giao dịch mua 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 cấu hình Adapty.
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 một paywall và một placement**: Trong Adapty Dashboard, tạo paywall trên trang **Paywalls**, sau đó gán nó vào một placement trên trang **Placements**. Trong code, placement ID là chuỗi bạn truyền vào `Adapty().getPaywall()`.
[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ó quyền truy cập vào 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 đã có đủ năm điều 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 các lời 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 luôn cập 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 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 [kho GitHub Context7](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 Flutter SDK
```
:::warning
Dù Context7 không còn 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 đú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 túy. Thêm `.md` vào cuối URL, hoặc nhấn **Copy for LLM** bên dưới tiêu đề bài viết. Ví dụ: [adapty-cursor-flutter.md](https://adapty.io/docs/vi/adapty-cursor-flutter.md).
Mỗi bước trong [hướng dẫn triển khai](#implementation-walkthrough) bên dưới đều có khối "Gửi cho LLM của bạn" với các link `.md` để dán vào.
Để xem thêm tài liệu cùng lúc, xem [các file index và tập hợp 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 sẽ đi qua việc tích hợp Adapty theo thứ tự triển khai. Mỗi bước bao gồm tài liệu cần gửi cho LLM, những gì bạn cần 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 bất kỳ code nào.
Hãy cho LLM biết cách bạn xử lý giao dịch mua — điều này ảnh hưởng đến các hướng dẫn mà nó cần theo dõi:
- [**Adapty Paywall Builder**](adapty-paywall-builder): Bạn tạo paywall trong trình xây dựng không cần code của Adapty, và SDK tự động hiển thị chúng.
- [**Paywall tự tạo**](flutter-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ý giao dịch mua.
- [**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](flutter-quickstart-paywalls).
### Cài đặt và cấu hình SDK \{#install-and-configure-the-sdk\}
Thêm dependency Adapty SDK bằng `flutter pub add` và kích hoạt nó với Public SDK key của bạn. Đây là nền tảng — không có gì khác hoạt động được nếu thiếu bước này.
**Hướng dẫn:** [Cài đặt & cấu hình Adapty SDK](sdk-installation-flutter)
Gửi nội dung này cho LLM của bạn:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/vi/sdk-installation-flutter.md
```
:::tip[Checkpoint]
- **Kết quả mong đợi:** Ứng dụng build và chạy được trên cả iOS và Android. Console debug 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ực của mình từ App settings chưa.
:::
### Hiển thị paywall và xử lý giao dịch mua \{#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. Các hướng dẫn bạn cần phụ thuộc vào cách bạn xử lý giao dịch mua.
Kiểm thử từng giao dịch mua trong sandbox khi bạn làm — đừng đợi đến cuối. Xem [Kiểm thử giao dịch mua trong sandbox](test-purchases-in-sandbox) để biết hướng dẫn thiết lập.
tùy chọn
mặc định: `en`
|Định danh của [bản địa hóa paywall](add-paywall-locale-in-adapty-paywall-builder). Tham số này là mã ngôn ngữ gồm một hoặc hai thẻ phụ được phân cách bằng dấu trừ (**-**). Thẻ phụ đầu tiên là ngôn ngữ, thẻ 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ã locale](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ừ máy chủ và trả về dữ liệu đã cache nếu 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 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ó kết nối internet không ổn định, hãy cân nhắc 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 thời gian tải sẽ nhanh hơn dù kết nối có kém đến đâu. Cache được cập nhật thường xuyên nên an toàn khi dùng trong phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn tồn tại khi khởi động lại ứng dụng và chỉ bị xóa khi cài đặt lại ứng dụng hoặc xóa 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 dùng CDN để tải paywall nhanh hơn và máy chủ dự phòng độc lập trong trường hợp CDN không tiếp cận đượ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 `loadTimeout` đã chỉ định, vì thao tác có thể bao gồm nhiều yêu cầu bên dưới.
Đối với Android: Bạn có thể tạo `TimeInterval` bằng các hàm mở rộng (như `5.seconds`, trong đó `.seconds` đến từ `import com.adapty.utils.seconds`), hoặc `TimeInterval.seconds(5)`. Để không giới hạn thời gian, dùng `TimeInterval.INFINITE`.
| Tham số phản hồi: | Tham số | Mô tả | | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Một đối tượng [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) với 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 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 nút **Show on device** trong paywall builder. Nếu tùy chọn này chưa đượ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ó chứa `ViewConfiguration` hay không — điều này cho biết paywall được tạo bằng Paywall Builder. Thông tin này sẽ hướng dẫn bạn cách hiển thị paywall. Nếu có `ViewConfiguration`, xử lý nó như paywall Paywall Builder; nếu không, [xử lý nó như paywall remote config](present-remote-config-paywalls-flutter). ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` Sau khi có view, [hiển thị paywall](flutter-present-paywalls). ## Lấy paywall cho đối tượng mặc định để tải nhanh hơn \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Thông thường, paywall được lấy gần như ngay lập tức nên 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 trường hợp bạn có nhiều đối tượng và paywall, và người dùng 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. Để giải quyết điều này, bạn có thể 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ư đã mô tả trong phần [Lấy thông tin paywall](flutter-get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) ở trên. :::warning Lý do chúng tôi khuyến nghị dùng `getPaywall` Phương thức `getPaywallForDefaultAudience` có một số hạn chế đáng kể: - **Vấn đề tương thích ngược tiềm ẩn**: Nếu bạn cần hiển thị các paywall khác nhau cho các phiên bản ứng dụng khác nhau (hiện tại và tương lai), bạn có thể gặp khó khăn. Bạn sẽ phải thiết kế paywall hỗ trợ phiên bản hiện tại (cũ) hoặc chấp nhận rằng người dùng dùng phiên bản hiện tại (cũ) có thể gặp sự cố với paywall không render được. - **Mất khả năng targeting**: Tất cả người dùng sẽ thấy cùng một paywall được thiết kế cho đối tượng **All Users**, nghĩa là bạn mất đi khả năng targeting cá nhân hóa (bao gồm theo quốc gia, attribution marketing hoặc các thuộc tính tùy chỉnh của bạn). Nếu bạn sẵn sàng chấp nhận những hạn chế này để được hưởng lợi từ việc lấy paywall nhanh hơn, dùng phương thức `getPaywallForDefaultAudience` như sau. Ngược lại, hãy dùng `getPaywall` như mô tả [ở trên](#fetch-paywall-designed-with-paywall-builder). ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` :::note Phương thức `getPaywallForDefaultAudience` có sẵn từ Flutter SDK phiên bản 3.2.0 trở lên. ::: | 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 là mã ngôn ngữ gồm một hoặc nhiều thẻ phụ được phân cách bằng dấu trừ (**-**). Thẻ phụ đầu tiên là ngôn ngữ, thẻ 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ã 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ố tải dữ liệu từ máy chủ và trả về dữ liệu đã cache nếu 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 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ó kết nối internet không ổn định, hãy cân nhắc 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 thời gian tải sẽ nhanh hơn dù kết nối có kém đến đâu. Cache được cập nhật thường xuyên nên an toàn khi dùng trong phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn tồn tại khi khởi động lại ứng dụng và chỉ bị xóa khi cài đặt lại ứng dụng hoặc xóa thủ công.
| ## Tùy chỉnh assets \{#customize-assets\} Để tùy chỉnh hình ảnh và video trong paywall, hãy triển khai custom assets. Hình ảnh hero và video có ID được xác định trước: `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ị hình ảnh preview cục bộ trong khi hình ảnh chính từ xa đang tải. - Hiển thị hình ảnh preview trước khi chạy video. :::important Để sử dụng tính năng này, hãy cập nhật Adapty Flutter SDK lên phiên bản 3.8.0 trở lên. ::: Đây là ví dụ về cách cung cấp custom assets thông qua một dictionary đơn giản: ```dart final customAssets = { // Show a local image using a custom ID 'custom_image': AdaptyCustomAsset.localImageAsset( assetId: 'assets/images/image_name.png', ), // Show a local video with a preview image 'hero_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/custom_video.mp4', ), }; try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customAssets:
## Số lượt xem paywall bị tính gấp đôi \{#the-paywall-view-number-is-too-big\}
**Vấn đề**: Số lượt xem paywall hiển thị gấp đôi so với dự kiến.
**Nguyên nhân**: Bạn có thể đang gọi `logShowPaywall` trong code, khiến số lượt xem bị tính trùng khi sử dụng Paywall Builder. Với các paywall được thiết kế bằng Paywall Builder, analytics được theo dõi tự động, vì vậy 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 vấn đề khác \{#other-issues\}
**Vấn đề**: Bạn đang gặp các sự cố liên quan đến Paywall Builder chưa được đề cập ở trên.
**Giải pháp**: Nếu cần, hãy migrate SDK lên phiên bản mới nhất theo [hướng dẫn migration](flutter-sdk-migration-guides). Nhiều sự cố đã được khắc phục trong các phiên bản SDK mới hơn.
---
# File: flutter-quickstart-manual
---
---
title: "Bật tính năng mua hàng trong paywall tùy chỉnh với Flutter SDK"
description: "Tích hợp Adapty SDK vào các paywall Flutter tùy chỉnh để 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. Giữ toàn quyền kiểm soát việc triển khai paywall, trong khi Adapty SDK tự động 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 triển khai 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 sử dụng [Adapty Paywall Builder](flutter-quickstart-paywalls). Với Paywall Builder, bạn tạo paywall trong 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 cứ thứ gì người dùng có thể mua (gói đăng ký, consumable, quyền truy cập trọn đời)
- [**Paywalls**](paywalls) – 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 sản phẩm, giá cả và ưu đãi mà không cần chỉnh sửa code ứng dụng.
- [**Placements**](placements) – nơi và thời điểm bạn hiển thị paywall trong ứng dụng (như `main`, `onboarding`, `settings`). Bạn thiết lập paywall cho placement trong dashboard, sau đó yêu cầu chúng theo placement ID trong code. Cách 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 những khái niệm này ngay cả khi làm việc với paywall tùy chỉnh. Về cơ bản, đây chỉ là cách bạn quản lý các sản phẩm bán trong ứng dụng của mình.
Để 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 sản phẩm của mình. Để hiểu những gì cần làm trên dashboard, hãy theo dõi 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 với hoặc không cần xác thực phía backend.
Tuy nhiên, Adapty SDK xử lý người dùng ẩn danh và đã 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](flutter-quickstart-identify) để hiểu rõ đặc điểm 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, bạn cần:
1. Lấy đối tượng `paywall` bằng cách truyền [placement](placements) ID 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`.
```dart showLineNumbers
Futuretù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 được kỳ vọng là một mã ngôn ngữ gồm một hoặc nhiều subtag được phân cách bằng dấu trừ (**-**). Subtag đầu tiên dành cho ngôn ngữ, subtag thứ hai dành cho vùng.
Ví dụ: `en` nghĩa là tiếng Anh, `pt-br` đại diện cho tiếng Bồ Đào Nha của Brazil.
Xem [Bản địa hóa và mã locale](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ừ 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 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 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 sẽ có trải nghiệm tải nhanh hơn, dù kết nối internet của họ có chập chờn thế nào. Cache được cập nhật thường xuyên, nên sử dụng trong phiên để tránh các yêu cầu mạng là hoàn toàn an toàn.
Lưu ý rằng cache vẫn còn nguyên sau 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 xóa thủ công.
Adapty SDK lưu trữ paywalls ở hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và [paywall dự phòng](flutter-use-fallback-paywalls). Chúng tôi cũng sử dụng CDN để lấy paywalls 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. 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, đồng thời đảm bảo độ tin cậy ngay cả khi kết nối internet yếu.
| | **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ị đã 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.
| Đừng hardcode ID sản phẩm! Vì paywalls đượ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 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 nên hiển thị cả 3 mà không cần thay đổi code. Thứ duy nhất bạn phải hardcode là placement ID. Tham số trả về: | Tham số | Mô tả | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Một đối tượng [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.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ó: ```dart showLineNumbers try { final products = await Adapty().getPaywallProducts(paywall: paywall); // the requested products array } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Tham số trả về: | Tham số | Mô tả | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Danh sách các đối tượng [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) gồm: định danh sản phẩm, tên sản phẩm, giá, tiền tệ, thời hạn gói đăng ký, và một số thuộc tính khác. | Khi tự thiết kế giao diện paywall, bạn thường cần truy cập các thuộc tính này từ đối tượng [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.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 đủ thông tin 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 đề sản phẩm, 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, chứ không phải locale của thiết bị. | | **Price** | Để hiển thị giá đã được bản địa hóa, dùng `product.price.localizedString`. Bản địa hóa này dựa trên thông tin locale 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, dùng `product.price.currencySymbol`. | | **Subscription Period** | Để hiển thị chu kỳ (ví dụ: tuần, tháng, năm, v.v.), dùng `product.subscription?.localizedPeriod`. Bản địa hóa này dựa trên locale của thiết bị. Để lấy chu kỳ gói đăng ký theo chương trình, dùng `product.subscription?.period`. Từ đó bạn có thể truy cập enum `unit` để lấy độ dài (tức là ngày, tuần, tháng, năm, hoặc không xác định). Giá trị `numberOfUnits` sẽ cho bạn biết số đơn vị chu kỳ. Ví dụ, đối với gói đăng ký theo quý, bạn sẽ thấy `AdaptyPeriodUnit.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 rằng gói đăng ký có ưu đãi giới thiệu, hãy xem thuộc tính `product.subscription?.offer?.phases`. Đây là một 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á ưu đãi. Trong mỗi đối tượng giai đoạn có các thuộc tính hữu ích sau: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 được kỳ vọng là một mã ngôn ngữ gồm một hoặc nhiều subtag được phân cách bằng dấu trừ (**-**). Subtag đầu tiên dành cho ngôn ngữ, subtag thứ hai dành cho vùng.
Ví dụ: `en` nghĩa là tiếng Anh, `pt-br` đại diện cho tiếng Bồ Đào Nha của Brazil.
Xem [Bản địa hóa và mã locale](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ừ 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 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 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 sẽ có trải nghiệm tải nhanh hơn, dù kết nối internet của họ có chập chờn thế nào. Cache được cập nhật thường xuyên, nên sử dụng trong phiên để tránh các yêu cầu mạng là hoàn toàn an toàn.
Lưu ý rằng cache vẫn còn nguyên sau 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 xóa thủ công.
| --- # File: present-remote-config-paywalls-flutter --- --- title: "Render paywall designed by remote config in Flutter SDK" description: "Khám phá cách hiển thị paywall Remote Config trong Adapty Flutter 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 render trong code của ứng dụng để hiển thị nó cho người dùng. Vì Remote Config linh hoạt theo nhu cầu của bạn, bạn hoàn toàn chủ động quyết định nội dung và giao diện của paywall. Chúng tôi cung cấp một phương thức để lấy cấu hình remote, giúp bạn tự do hiển thị paywall tùy chỉnh đã được thiết lập 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, hãy truy cập thuộc tính `remoteConfig` và trích xuất các giá trị cần thiết. ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(id: "YOUR_PLACEMENT_ID"); final String? headerText = paywall.remoteConfig?.dictionary?['header_text'] as String?; } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 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 thành một trang trực quan hấp dẫn. Hãy đảm bảo thiết kế tương thích với nhiều kích thước màn hình và hướng xoay khác nhau, 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-flutter#track-paywall-view-events) như mô tả bên dưới, để Adapty analytics có thể thu thập dữ liệu cho funnel và A/B test. ::: Sau khi hiển thị paywall xong, tiếp tục thiết lập flow thanh toán. 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. Xem chi tiết về phương thức `.makePurchase()` tại [Thực hiện mua hàng](flutter-making-purchases). Chúng tôi khuyến nghị [tạo một paywall dự phòng gọi là fallback paywall](flutter-use-fallback-paywalls). Paywall dự phòng 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 đó. ## Theo dõi sự kiện xem paywall \{#track-paywall-view-events\} Adapty giúp bạn đo lường hiệu suất của các paywall. Trong khi dữ liệu mua hàng được thu thập tự độ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 khách hàng nhìn thấy một paywall. Để ghi lại sự kiện xem paywall, chỉ cần gọi `.logShowPaywall(paywall)`, và nó sẽ được phản ánh trong các chỉ số 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). ::: ```dart showLineNumbers try { final result = await Adapty().logShowPaywall(paywall: paywall); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Tham số yêu cầu: | Tham số | Bắt buộc | Mô tả | | :---------- | :------- |:----------------------------------------------------------------------| | **paywall** | bắt buộc | Một đối tượng [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). | --- # File: flutter-making-purchases --- --- title: "Thực hiện mua hàng trong ứng dụng di động với Flutter 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 di động là bước thiết yếu để 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ỉ cần hiển thị paywall là đủ để hỗ trợ mua hàng nếu bạn sử dụng [Paywall Builder](adapty-paywall-builder) để tùy chỉnh paywall. Nếu bạn không sử dụng Paywall Builder, bạn cần dùng một phương thức riêng là `.makePurchase()` để hoàn tất giao dịch mua 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à tiến hành các giao dịch họ muốn. 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 cố 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 sẽ chỉ được áp dụng tự động nếu bạn sử dụng paywall được thiết lập bằng Paywall Builder. Trong các trường hợp khác, bạn cần [xác minh điều kiện nhận ưu đãi giới thiệu của người dùng 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 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 thành cấu hình ban đầu](quickstart) mà không bỏ qua 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. ## Thực hiện mua hàng \{#make-purchase\} :::note **Đang dùng [Paywall Builder](adapty-paywall-builder)?** Các giao dịch mua được xử lý tự động — bạn có thể bỏ qua bước này. **Muốn có hướng dẫn từng bước?** Xem [hướng dẫn quickstart](flutter-implement-paywalls-manually) để biết hướng dẫn triển khai đầy đủ từ đầu đến cuối. ::: ```dart showLineNumbers try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): if (profile.accessLevels['premium']?.isActive ?? false) { // Grant access to the paid features } break; case AdaptyPurchaseResultPending(): break; case AdaptyPurchaseResultUserCancelled(): break; default: break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` Tham số yêu cầu: | Tham số | Bắt buộc | Mô tả | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | bắt buộc | Đối tượng [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) được 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://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html) cung cấp thông tin toàn diện về các mức độ truy cập, gói đăng ký và 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 phiên bản StoreKit của Apple thấp hơn v2.0 và phiên bản Adapty SDK 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 deprecated. ::: ## 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 thức hoạt động phụ thuộc vào cửa hàng: - Đối với App Store, gói đăng ký được cập nhật tự động trong nhóm đăng ký. Nếu người dùng mua một gói đăng ký từ nhóm này trong khi đã có gói từ nhóm khác, cả hai gói đăng ký sẽ hoạt động đồng thời. - Đối với Google Play, gói đăng ký không được cập nhật tự động. Bạn cần quản lý việc chuyển đổi trong mã nguồn ứng dụng di độ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, hãy gọi phương thức `.makePurchase()` với tham số bổ sung: ```dart showLineNumbers try { final result = await adapty.makePurchase( product: product, subscriptionUpdateParams: subscriptionUpdateParams, ); // successful cross-grade } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` Tham số yêu cầu bổ sung: | Tham số | Bắt buộc | Mô tả | | :--------------------------- | :------- |:--------------------------------------------------------------------------------------------------------| | **subscriptionUpdateParams** | bắt buộc | Đối tượng [`AdaptyAndroidSubscriptionUpdateParameters`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyAndroidSubscriptionUpdateParameters-class.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ị từ 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ỉ khả dụng cho việc nâng cấp gói đăng ký. Hạ cấp không được hỗ trợ. - Chế độ thay thế [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Lưu ý: Thay đổi gói đăng ký thực sự sẽ chỉ xảy ra khi chu kỳ thanh toán của gói đăng ký 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';Một đối tượng [`AdaptyProfile`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.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-flutter --- --- title: "Triển khai chế độ Observer trong Flutter SDK" description: "Triển khai chế độ observer trong Adapty để theo dõi các sự kiện gói đăng ký của người dùng trong Flutter SDK." --- Nếu bạn đã có hệ thống xử lý mua hàng riêng và chưa sẵn sàng chuyển hoàn toàn sang Adapty, bạn có thể khám phá [chế độ Observer](observer-vs-full-mode). Ở dạng cơ bản, chế độ Observer cung cấp tính năng phân tích nâng cao và tích hợp liền mạch với các hệ thống attribution và analytics. Nếu đây là những gì bạn cần, bạn chỉ cần: 1. Bật chế độ 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 [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk). 2. [Báo cáo giao dịch](report-transactions-observer-mode-flutter) từ hệ thống mua hàng hiện có của bạn lên Adapty. ## Thiết lập chế độ Observer \{#observer-mode-setup\} Bật chế độ Observer 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 ở chế độ Observer, 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 đó. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withObserverMode(true) // Enable observer mode ..withLogLevel(AdaptyLogLevel.verbose), ); ``` Tham số: | Tham số | Mô tả | | --------------------------- | ------------------------------------------------------------ | | observerMode | Giá trị boolean kiểm soát [chế độ Observer](observer-vs-full-mode). Giá trị mặc định là `false`. | ## Sử dụng paywall của Adapty trong chế độ Observer \{#using-adapty-paywalls-in-observer-mode\} Nếu bạn cũng muốn sử dụng các 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ố bước thiết lập trong chế độ Observer. Đây là những gì bạn cần làm ngoài các bước đã nêu ở trên: 1. Hiển thị paywall như bình thường đối với [paywall dùng Remote Config](present-remote-config-paywalls-flutter). 3. [Liên kết paywall](report-transactions-observer-mode-flutter) với các giao dịch mua hàng. --- # File: report-transactions-observer-mode-flutter --- --- title: "Báo cáo giao dịch trong Observer Mode với Flutter 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 với Flutter SDK." ---phoneNumber
firstName
lastName
| String | | gender | Enum, các giá trị cho phép: `female`, `male`, `other` | | birthday | Date | ### Thuộc tính tùy chỉnh của người dùng \{#custom-user-attributes\} Bạn có thể thiết lập 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 ngoại ngữ, đó 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. ```javascript showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..setCustomStringAttribute('value1', 'key1') ..setCustomDoubleAttribute(1.0, 'key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Để xóa một key hiện có, dùng phương thức `.withRemoved(customAttributeForKey:)`: ```javascript showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..removeCustomAttribute('key1') ..removeCustomAttribute('key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Đô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 cập nhật kịp thời, vì thuộc tính người dùng có thể được gửi từ nhiều thiết bị khác nhau bất kỳ lúc nào, do đó các thuộc tính trên server có thể đã thay đổi kể từ lần đồng bộ 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 tối đa 50 ký tự. --- # File: flutter-listen-subscription-changes --- --- title: "Kiểm tra trạng thái đăng ký trong Flutter SDK" description: "Theo dõi và quản lý trạng thái gói đăng ký của người dùng trong Adapty để cải thiện khả năng giữ chân khách hàng trong ứng dụng Flutter của bạn." --- Với Adapty, việc theo dõi trạng thái gói đăng ký trở nên dễ dàng hơn bao giờ hết. Bạn không cần phải chèn thủ công 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.Một đối tượng [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.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ơ người dùng để xác định xem người dùng có quyền truy cập premium vào ứng dụng hay không.
Phương thức `.getProfile` luôn cố gắng truy vấn API nên sẽ trả về kết quả mới nhất. 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ừ server, dữ liệu từ cache sẽ được trả về thay thế. Ngoài ra, Adapty SDK cũng thường xuyên cập nhật cache của `AdaptyProfile` để đảm bảo thông tin luôn ở trạng thái mới nhất.
| Phương thức `.getProfile()` cung cấp hồ sơ người dùng, từ đó bạn có thể kiểm tra trạng thái mức độ truy cập. Một ứng dụng có thể có nhiều mức độ truy cập. Chẳng hạn, nếu bạn có ứng dụng đọc báo và bán gói đăng ký theo từng chủ đề riêng biệt, bạn có thể tạo các mức độ truy cập "sports" và "science". Tuy nhiên, trong hầu hết trường hợp, bạn chỉ cần một mức độ truy cập duy nhất — khi đó, hãy 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 mặc định "premium": ```javascript showLineNumbers try { final profile = await Adapty().getProfile(); if (profile?.accessLevels['premium']?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Lắng nghe cập nhật trạng thá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 thêm một số cấu hình: ```javascript showLineNumbers Adapty().didUpdateProfileStream.listen((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 đă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 server không khả dụng, dữ liệu 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ơ người dùng. Tuy nhiên, cần lưu ý rằng không thể yêu cầu dữ liệu trực tiếp từ cache. SDK định kỳ truy vấn server mỗi phút để kiểm tra có cập nhật hay thay đổi nào liên quan đến hồ sơ người dùng không. 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 cache luôn nhất quán với server. --- # File: flutter-deal-with-att --- --- title: "Xử lý ATT trong Flutter SDK" description: "Bắt đầu với Adapty trên Flutter để đơ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 cho 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/) đó đến Adapty. ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setAppTrackingTransparencyStatus(AdaptyIOSAppTrackingTransparencyStatus.authorized); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` :::warning 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 đến các tích hợp bạn đã cấu hình. ::: --- # File: kids-mode-flutter --- --- title: "Chế độ Trẻ em trong Flutter SDK" description: "Dễ dàng bật Chế độ Trẻ em để 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 Flutter SDK." --- Nếu ứng dụng Flutter 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 đang sử dụng Adapty SDK, bạn chỉ cần thực hiện vài bước đơn giả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. ## Cần làm gì? \{#whats-required\} Bạn cần cấu hình Adapty SDK để tắt việc 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, hãy cẩn thận khi sử dụng customer user ID. ID người dùng có định dạng `tùy chọn
mặc định: `en`
|Định danh của bản địa hóa onboarding. Tham số này là một mã ngôn ngữ gồm một hoặc hai thẻ con phân tách bằng dấu gạch ngang (**-**). 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 khi gặp lỗi. Chúng tôi khuyến nghị tùy chọn này vì nó đảm bảo người dùng 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 thường xuyên gặp tình trạng internet không ổn định, hãy cân nhắc 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ẽ tải nhanh hơn dù kết nối có chập chờn. Cache được cập nhật thường xuyên, nên sử dụng 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 giữ nguyên khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt hoặc xóa thủ công.
Adapty SDK lưu trữ onboarding cục bộ theo 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 dùng CDN để tải onboarding 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 onboarding 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 `loadTimeout` đã chỉ định, vì thao tác có thể bao gồm nhiều yêu cầu bên dưới.
| Tham số phản hồi: | Tham số | Mô tả | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Một đối tượng [`AdaptyOnboarding`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyOnboarding-class.html) bao gồm: định danh và cấu hình của onboarding, Remote Config, và một số thuộc tính khác. | ## 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, nên 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 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ị 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. Để giải quyết điều này, bạn có thể 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ị vẫn là lấy onboarding bằng phương thức `getOnboarding`, như mô tả chi tiết trong phần [Lấy onboarding](#fetch-onboarding) ở trên. :::warning Hãy cân nhắc 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ể 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 đúng. - **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 theo quốc gia, attribution, hoặc thuộc tính tùy chỉnh. Nếu tốc độ lấy nhanh hơn vượt trội hơn những hạn chế này trong 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). ::: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboardingForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` 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 onboarding. Tham số này là một mã ngôn ngữ gồm một hoặc hai thẻ con phân tách bằng dấu gạch ngang (**-**). 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 khi gặp lỗi. Chúng tôi khuyến nghị tùy chọn này vì nó đảm bảo người dùng 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 thường xuyên gặp tình trạng internet không ổn định, hãy cân nhắc 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ẽ tải nhanh hơn dù kết nối có chập chờn. Cache được cập nhật thường xuyên, nên sử dụng 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 giữ nguyên khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt hoặc xóa thủ công.
Adapty SDK lưu trữ onboarding cục bộ theo 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 dùng CDN để tải onboarding 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 onboarding trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet kém.
| --- # File: flutter-present-onboardings --- --- title: "Hiển thị onboarding trong Flutter 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 một onboarding bằng builder, bạn không cần lo lắng về việc render nó trong code Flutter của mình để hiển thị cho người dùng. Onboarding đó đã bao gồm cả nội dung cần hiển thị lẫn cách thức hiển thị. Trước khi bắt đầu, hãy đảm bảo rằng: 1. Bạn đã cài đặt [Adapty Flutter SDK](sdk-installation-flutter) phiên bản 3.8.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). Adapty Flutter SDK cung cấp hai cách để hiển thị onboarding: - **Màn hình độc lập** - **Widget nhúng** ## Hiển thị dưới dạng màn hình độc lập \{#present-as-standalone-screen\} Để hiển thị onboarding dưới dạng màn hình độc lập, sử dụng phương thức `onboardingView.present()` trên `onboardingView` đượ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 onboarding, hãy gọi `createOnboardingView` thêm một lần nữa để tạo một instance `onboardingView` mới. :::warning Tái sử dụng cùng một `onboardingView` mà không tạo lại có thể dẫn đến lỗi `AdaptyUIError.viewAlreadyPresented`. ::: ```javascript showLineNumbers title="Flutter" try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Đóng onboarding \{#dismiss-the-onboarding\} Khi bạn cần đóng onboarding theo cách lập trình, hãy sử dụng phương thức `dismiss()`: ```dart showLineNumbers title="Flutter" try { await onboardingView.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // 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 giá trị `AdaptyUIIOSPresentationStyle.fullScreen` (mặc định) hoặc `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await onboardingView.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Nhúng vào cây widget \{#embed-in-widget-hierarchy\} Để nhúng onboarding vào cây widget hiện có, sử dụng widget `AdaptyUIOnboardingPlatformView` trực tiếp trong cây widget Flutter của bạn. ```javascript showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, // The onboarding object you fetched onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` :::note Để platform view trên Android hoạt động, hãy đảm bảo `MainActivity` của bạn kế thừa `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: ## Loader trong quá trình tải onboarding \{#loader-during-onboarding\} Khi hiển thị onboarding, bạn có thể nhận thấy một màn hình tải ngắn giữa splash screen và onboarding trong khi view bên dưới đang được khởi tạo. Bạn có thể xử lý điều này theo nhiều cách khác nhau tùy theo nhu cầu. #### Kiểm soát splash screen bằng onDidFinishLoading \{#control-splash-screen-using-ondidfinishloading\} :::note Cách này chỉ khả dụng khi nhúng onboarding dưới dạng widget. Không áp dụng cho màn hình hiển thị độc lập. ::: Cách tiếp cận đa nền tảng được khuyến nghị là giữ splash screen hoặc overlay tùy chỉnh hiển thị cho đến khi onboarding được tải đầy đủ, sau đó ẩn nó thủ công. Khi sử dụng widget nhúng, hãy đặt widget của bạn phủ lên trên và ẩn overlay khi `onDidFinishLoading` được kích hoạt: ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Hide your custom splash screen or overlay here }, // ... other callbacks ) ``` ### Tùy chỉnh loader mặc định \{#customize-native-loader\} :::important Cách này phụ thuộc vào từng nền tảng và yêu cầu duy trì code UI native. Không được khuyến nghị trừ khi bạn đã duy trì các native layer riêng biệt trong ứng dụng của mình. ::: Nếu bạn cần tùy chỉnh loader mặc định, bạn có thể thay thế nó bằng các layout theo từng nền tảng. Cách này yêu cầu triển khai riêng cho Android và iOS: - **iOS**: Thêm `AdaptyOnboardingPlaceholderView.xib` vào dự án Xcode của bạn - **Android**: Tạo `adapty_onboarding_placeholder_view.xml` trong `res/layout` và định nghĩa một placeholder ở đó ## Tùy chỉnh cách mở liên kết trong onboarding \{#customize-how-links-open-in-onboardings\} :::important Tùy chỉnh cách mở liên kết trong onboarding được hỗ trợ từ Adapty SDK v3.15.1 trở lên. ::: Theo mặc định, các liên kết trong onboarding mở trong trình duyệt trong ứng dụng. Điều này 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 của bạn, cho phép người dùng xem mà không cần chuyển sang ứng dụng khác. Nếu bạn muốn mở liên kết trong trình duyệt bên ngoài thay thế, bạn có thể tùy chỉnh hành vi này bằng cách đặt tham số `externalUrlsPresentation` thành `AdaptyWebPresentation.externalBrowser`:
Sau đó, bạn có thể sử dụng ID này trong code và xử lý nó như một custom action. 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 delegate `onboardingController` sẽ được kích hoạt với case `.custom(id:)` và tham số `actionId` chính là **Action ID** từ builder. Bạn có thể tạo ID riêng của mình, như "allowNotifications".
```javascript
// Full-screen presentation
void onboardingViewOnCustomAction(
AdaptyUIOnboardingView view,
AdaptyUIOnboardingMeta meta,
String actionId,
) {
switch (actionId) {
case 'login':
_login();
break;
case 'allow_notifications':
_allowNotifications();
break;
}
}
// Embedded widget
onCustomAction: (meta, actionId) {
_handleCustomAction(actionId);
}
```
:::important
Lưu ý rằng bạn cần tự quản lý những 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 đó.
:::
```javascript showLineNumbers title="Flutter"
// Full-screen presentation
void onboardingViewOnCloseAction(
AdaptyUIOnboardingView view,
AdaptyUIOnboardingMeta meta,
String actionId,
) {
await view.dismiss();
}
// Embedded widget
onCloseAction: (meta, actionId) {
Navigator.of(context).pop();
}
```
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.
3. Chọn sản phẩm bạn đang kiểm tra.
4. Cuộn xuống 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. Tiếp tục vào 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 mục **Current Pricing for New Subscribers**.
5. Đảm bảo tất cả các mức giá cần thiết đã đượ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 hiệu lực \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\}
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 có đều hiển thị là **Active** không.
Làm theo các bước trên, bạn sẽ có thể khắc phục 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 \{#step-6-recreate-the-product-if-its-stuck\}
Các bước 1–5 có thể đều vượt qua — 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 xuất hiện trên đường dẫn 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 ID sản phẩm. Chờ tối đa 24 giờ sau khi tạo lại để các thay đổi được lan truyền.
---
# File: cantMakePayments-flutter
---
---
title: "Sửa lỗi Code-1003 cantMakePayment trong Flutter SDK"
description: "Giải quyết lỗi 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: flutter-migration-guide-310
---
---
title: "Hướng dẫn migration lên Flutter Adapty SDK 3.10.0"
description: ""
---
Adapty SDK 3.10.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. Cập nhật phương thức `makePurchase` để sử dụng `AdaptyPurchaseParameters` thay vì các tham số riêng lẻ.
2. Thay thế `vendorProductIds` bằng `productIdentifiers` trong model `AdaptyPaywall`.
## Cập nhật phương thức makePurchase \{#update-makepurchase-method\}
Phương thức `makePurchase` hiện 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 kiểu dữ liệu an toàn hơn và cho phép mở rộng các tham số mua hàng trong tương lai.
```diff showLineNumbers
- final purchaseResult = await adapty.makePurchase(
- product: product,
- subscriptionUpdateParams: subscriptionUpdateParams,
- isOfferPersonalized: true,
- );
+ final parameters = AdaptyPurchaseParametersBuilder()
+ ..setSubscriptionUpdateParams(subscriptionUpdateParams)
+ ..setIsOfferPersonalized(true)
+ ..setObfuscatedAccountId('your-account-id')
+ ..setObfuscatedProfileId('your-profile-id');
+ final purchaseResult = await adapty.makePurchase(
+ product: product,
+ parameters: parameters.build(),
+ );
```
Nếu không cần thêm tham số nào, bạn có thể sử dụng đơn giản như sau:
```dart showLineNumbers
final purchaseResult = await adapty.makePurchase(
product: product,
);
```
## Cập nhật cách sử dụng model AdaptyPaywall \{#update-adaptywall-model-usage\}
Thuộc tính `vendorProductIds` đã bị deprecated và được 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
- paywall.vendorProductIds.map((vendorId) =>
- ListTextTile(title: vendorId)
- ).toList()
+ paywall.productIdentifiers.map((productId) =>
+ ListTextTile(title: productId.vendorProductId)
+ ).toList()
```
Đối tượng `AdaptyProductIdentifier` cung cấp quyền truy cập vào vendor product ID thông qua thuộc tính `vendorProductId`, giữ nguyên chức năng như cũ trong khi cung cấp cấu trúc tốt hơn cho các cải tiến trong tương lai.
## Tương thích ngược \{#backward-compatibility\}
Cả hai thay đổi đều duy trì tương thích ngược:
- Các tham số cũ trong `makePurchase` đã bị deprecated nhưng vẫn hoạt động bình thường
- Thuộc tính `vendorProductIds` đã bị deprecated nhưng vẫn có thể truy cập được
- Code hiện tại sẽ tiếp tục hoạt động, mặc dù bạn sẽ thấy các cảnh báo deprecation
Chúng tôi khuyến nghị cập nhật code của bạn để sử dụng các API mới nhằm đảm bảo khả năng tương thích trong tương lai và tận dụng tính an toàn kiểu dữ liệu và khả năng mở rộng được cải thiện.
---
# File: flutter-migration-guide-38
---
---
title: "Migrate Adapty Flutter SDK to v3.8"
description: "Migrate sang Adapty Flutter SDK v3.8 để cải thiện hiệu suất và có thêm tính năng monetization mới."
---
Adapty SDK 3.8.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. Cập nhật tên class observer và tên phương thức.
2. Cập nhật tên phương thức paywall dự phòng.
3. Cập nhật tên class view trong các phương thức xử lý sự kiện.
## Cập nhật tên class observer và tên phương thức \{#update-observer-class-and-method-names\}
Tên class observer và phương thức đăng ký của nó đã được đổi tên:
```diff showLineNumbers
- class MyObserver extends AdaptyUIObserver {
+ class MyObserver extends AdaptyUIPaywallsEventsObserver {
@override
void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) {
// Handle action
}
}
// Register observer
- AdaptyUI().setObserver(this);
+ AdaptyUI().setPaywallsEventsObserver(this);
```
## Cập nhật tên phương thức paywall dự phòng \{#update-fallback-paywalls-method-name\}
Phương thức đặt paywall dự phòng đã được đơn giản hóa:
```diff showLineNumbers
try {
- await Adapty.setFallbackPaywalls(assetId);
+ await Adapty.setFallback(assetId);
} on AdaptyError catch (adaptyError) {
// handle the error
} catch (e) {
// handle the error
}
```
## Cập nhật tên class view trong các phương thức xử lý sự kiện \{#update-view-class-name-in-event-handling-methods\}
Tất cả các phương thức xử lý sự kiện hiện sử dụng class `AdaptyUIPaywallView` mới thay cho `AdaptyUIView`:
```diff showLineNumbers
- void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action)
+ void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action)
- void paywallViewDidSelectProduct(AdaptyUIView view, AdaptyPaywallProduct product)
+ void paywallViewDidSelectProduct(AdaptyUIPaywallView view, AdaptyPaywallProduct product)
- void paywallViewDidStartPurchase(AdaptyUIView view, AdaptyPaywallProduct product)
+ void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product)
- void paywallViewDidFinishPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyProfile profile)
+ void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyProfile profile)
- void paywallViewDidFailPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyError error)
+ void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error)
- void paywallViewDidFinishRestore(AdaptyUIView view, AdaptyProfile profile)
+ void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile)
- void paywallViewDidFailRestore(AdaptyUIView view, AdaptyError error)
+ void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error)
- void paywallViewDidFailLoadingProducts(AdaptyUIView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error)
+ void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error)
- void paywallViewDidFailRendering(AdaptyUIView view, AdaptyError error)
+ void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error)
```
---
# File: migration-to-flutter-sdk-34
---
---
title: "Migrate Adapty Flutter SDK to v3.4"
description: "Migrate to Adapty Flutter SDK v3.4 for better performance and new monetization features."
---
Adapty SDK 3.4.0 là một bản phát hành lớn với các cải tiến yêu cầu bạn thực hiện các bước migration.
## Cập nhật các 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 xuống 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](flutter-use-fallback-paywalls) bằng các file mới.
## Cập nhật cách triển khai 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ách triển khai 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` nê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 nó đượ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 tích hợp.
:::
```diff showLineNumbers
- // every time when calling transaction.finish()
- if (Platform.isAndroid) {
- try {
- await Adapty().restorePurchases();
- } on AdaptyError catch (adaptyError) {
- // handle the error
- } catch (e) {
- }
- }
try {
// every time when calling transaction.finish()
await Adapty().reportTransaction(
"YOUR_TRANSACTION_ID",
variationId: "PAYWALL_VARIATION_ID", // optional
);
} on AdaptyError catch (adaptyError) {
// handle the error
} catch (e) {
// handle the error
}
```
---
# File: migration-to-flutter330
---
---
title: "Migrate Adapty Flutter SDK sang v3.3"
description: "Migrate sang Adapty Flutter SDK v3.3 để cải thiện hiệu suất và các tính năng monetization mới."
---
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. Cập nhật phương thức cung cấp paywall dự phòng.
2. Xóa phương thức `getProductsIntroductoryOfferEligibility`.
3. Cập nhật cấu hình tích hợp cho Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase and Google Analytics, Mixpanel, OneSignal, Pushwoosh.
4. Cập nhật cài đặt Observer mode.
## Cập nhật phương thức cung cấp paywall dự phòng \{#update-method-for-providing-fallback-paywalls\}
Trước đây, phương thức yêu cầu paywall dự phòng dưới dạng chuỗi JSON (`jsonString`), nhưng bây giờ nó nhận đường dẫn đến file dự phòng cục bộ (`assetId`) thay thế.
```diff showLineNumbers
import 'dart:async' show Future;
import 'dart:io' show Platform;
-import 'package:flutter/services.dart' show rootBundle;
-final filePath = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json';
-final jsonString = await rootBundle.loadString(filePath);
+final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json';
try {
- await adapty.setFallbackPaywalls(jsonString);
+ await adapty.setFallbackPaywalls(assetId);
} on AdaptyError catch (adaptyError) {
// handle the error
} catch (e) {
}
```
Để xem ví dụ code đầy đủ, hãy xem trang [Sử dụng paywall dự phòng](flutter-use-fallback-paywalls).
## Xóa phương thức `getProductsIntroductoryOfferEligibility` \{#remove-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.
Bây giờ, đố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 cấu hình SDK tích hợp bên thứ ba \{#update-third-party-integration-sdk-configuration\}
Để đảm bảo các tích hợp hoạt động đúng với Adapty Flutter SDK 3.3.0 trở lên, hãy cập nhật cấu hình SDK của bạn cho các tích hợp sau theo mô tả trong các mục 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 [Cấu hình SDK cho tích hợp Adjust](adjust#connect-your-app-to-adjust).
```diff showLineNumbers
import 'package:adjust_sdk/adjust.dart';
import 'package:adjust_sdk/adjust_config.dart';
try {
final adid = await Adjust.getAdid();
if (adid == null) {
// handle the error
}
+ await Adapty().setIntegrationIdentifier(
+ key: "adjust_device_id",
+ value: adid,
+ );
final attributionData = await Adjust.getAttribution();
var attribution = MapGiá trị boolean kiểm soát [Observer mode](observer-vs-full-mode). Bật tùy chọn này nếu bạn tự xử lý giao dịch mua và trạng thái gói đăng ký, và sử dụng Adapty để gửi sự kiện gói đăng ký và analytics.
Giá trị mặc định là `false`.
🚧 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ý việc này.
| | **withCustomerUserId** | tùy chọn | Mã định danh người dùng trong hệ thống của bạn. Chúng tôi gửi nó trong các sự kiện gói đăng ký và analytics để gán sự kiện đúng với hồ sơ người dùng. Bạn cũng có thể tìm kiếm người dùng theo `customerUserId` trong menu [**Profiles and Segments**](https://app.adapty.io/profiles/users). | | **withIdfaCollectionDisabled** | tùy chọn |Đặt thành `true` để tắt tính năng thu thập và chia sẻ IDFA.
Chia sẻ địa chỉ IP của người dùng.
Giá trị mặc định là `false`.
Để biết thêm chi tiết về việc thu thập IDFA, hãy xem phần [Tích hợp Analytics](analytics-integration#disable-collection-of-advertising-identifiers).
| | **withIpAddressCollectionDisabled** | tùy chọn |Đặt thành `true` để tắt tính năng thu thập và chia sẻ địa chỉ IP của người dùng.
Giá trị mặc định là `false`.
| ### Kích hoạt module AdaptyUI của Adapty SDK \{#activate-adaptyui-module-of-adapty-sdk\} Bạn chỉ cần cấu hình module AdaptyUI nếu có kế hoạch sử dụng [Paywall Builder](adapty-paywall-builder): ```dart showLineNumbers title="Dart" try { final mediaCache = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 100 * 1024 * 1024, // 100MB memoryStorageCountLimit: 2147483647, // 2^31 - 1, max int value in Dart diskStorageSizeLimit: 100 * 1024 * 1024, // 100MB ); await AdaptyUI().activate( configuration: AdaptyUIConfiguration(mediaCache: mediaCache), observer:
### 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 động (ví dụ: sau khi họ đăng nhập vào ứng dụng hoặc đăng ký), hãy dùng phương thức `identify` để đặt customer user ID của họ.
- Nếu bạn **chưa sử dụng customer user ID này trước đây**, Adapty sẽ tự động liên kết nó với hồ sơ 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ơ đượ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.
:::
Luôn `await` `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 sẽ hoạt động trên hồ sơ ẩn danh. Xem [Thứ tự gọi trong iOS SDK](ios-sdk-call-order).
Theo mặc định, SDK sẽ cố tải dữ liệu từ máy chủ và trả về dữ liệu đã lưu trong cache nếu thất bại. Chúng tôi khuyên dùng tùy chọn này vì nó đảm bảo người dùng 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 thường gặp tình trạng kết nối internet không ổn định, hãy cân nhắc dùng `.returnCacheDataElseLoad` để trả về dữ liệu đã lưu trong 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 sẽ trải nghiệm tốc độ tải nhanh hơn dù kết nối có kém đến đâu. Cache được cập nhật định kỳ, 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à hoàn toàn an toàn.
Lưu ý rằng cache vẫn được giữ nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi cài đặt lại ứng dụng hoặc thực hiện 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 định kỳ như mô tả ở trên và [paywall dự phòng](fallback-paywalls). Chúng tôi cũng sử dụng CDN để tải 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. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản paywall mới nhất trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet yếu.
| | **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 đã lưu trong 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ể timeout muộn hơn một chút so với giá trị được chỉ định trong `loadTimeout`, vì thao tác này 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ả | | :-------- | :---------- | | Flow | Một đối tượng `AdaptyFlow` chứa placement, các định danh (`id`, `variationId`), tên, Remote Config, và cờ `hasViewConfiguration` cho biết flow có bao gồm cấu hình giao diện hay không. Để lấy các sản phẩm thực tế phục vụ việc tải trước, giao diện tùy chỉnh, hoặc kiểm tra theo chương trình, hãy gọi `getPaywallProducts(flow:)`. | ## Lấy cấu hình giao diện \{#fetch-the-view-configuration\} Sau khi lấy flow hoặc paywall, hãy kiểm tra xem nó có bao gồm cấu hình giao diện hay không thông qua `flow.hasViewConfiguration`. Cờ này cho biết cách placement được thiết kế trong Adapty Dashboard: - **`true`** — placement được thiết kế trong **Flow Builder** (một flow) hoặc **Paywall Builder** (một paywall). Adapty sẽ tự động hiển thị giao diện cho bạn. Tiếp tục với các bước bên dưới để lấy cấu hình giao diện và [hiển thị flow hoặc paywall](ios-present-paywalls). - **`false`** — placement là một paywall tùy chỉnh không có giao diện Builder. Sử dụng phương thức `getFlowConfiguration` để tải cấu hình view. ```swift showLineNumbers guard flow.hasViewConfiguration else { // handle as remote config paywall return } let flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow) ``` Các tham số: | Tham số | Bắt buộc | Mô tả | | :----------------------- | :------------- | :---------- | | **forFlow** | bắt buộc | Một đối tượng `AdaptyFlow` lấy được qua `Adapty.getFlow`. | | **locale** |tùy chọn
mặc định: `nil`
| Mã định danh của [bản địa hóa paywall](add-paywall-locale-in-adapty-paywall-builder). Nhập dưới dạng mã ngôn ngữ với một hoặc hai subtag phân cách bằng `-` (ví dụ: `en`, `pt-br`). Xem [Bản địa hóa và mã locale](localizations-and-locale-codes). | | **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ể timeout muộn hơn một chút so với giá trị được chỉ định trong `loadTimeout`, do thao tác có thể bao gồm nhiều request khác nhau bên dưới. | | **products** | tùy chọn | Cung cấp một mảng các đối tượng `AdaptyPaywallProduct` để tối ưu hóa thời điểm hiển thị sản phẩm trên màn hình. Nếu truyền `nil`, AdaptyUI sẽ tự động fetch các sản phẩm cần thiết. | | **systemRequestsHandler** | tùy chọn | Một đối tượng tuân theo `AdaptySystemRequestsHandler` để xử lý các yêu cầu quyền hệ thống và đánh giá được kích hoạt bởi các hành động trong flow. Chỉ cần thiết nếu flow của bạn có các hành động như vậy. | | **assetsResolver** | tùy chọn | Một dictionary `[String: AdaptyCustomAsset]` dùng để ghi đè hình ảnh và video trong flow/paywall. Xem [Tùy chỉnh assets](#customize-assets). | | **timerResolver** | tùy chọn | Một đối tượng tuân theo `AdaptyTimerResolver` cung cấp ngày kết thúc cho các bộ đếm thời gian do nhà phát triển định nghĩa. Xem [Thiết lập bộ đếm thời gian do nhà phát triển định nghĩa](#set-up-developer-defined-timers). | Sau khi tải xong, [hiển thị flow/paywall](ios-present-paywalls). ## Lấy flow hoặc paywall cho đối tượng mặc định để tải nhanh hơn \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Thông thường, flow và paywall được tải gần như ngay lập tức, nên 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 trường hợp bạn có nhiều đối tượng và placement, và người dùng của bạn có kết nối internet yếu, việc tải flow hoặc 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 flow hoặc 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ị gì cả. Để giải quyết vấn đề này, bạn có thể dùng phương thức `getFlowForDefaultAudience`, phương thức này lấy flow hoặc 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 flow hoặc paywall bằng phương thức `getFlow`, như đã trình bày trong phần [Lấy thông tin Paywall](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) ở trên. :::warning Lý do chúng tôi khuyến nghị dùng `getFlow` Phương thức `getFlowForDefaultAudience` có một số hạn chế đáng kể: - **Vấn đề tương thích ngược tiềm ẩn**: Nếu bạn cần hiển thị các paywall khác nhau cho các phiên bản ứng dụng khác nhau (hiện tại và tương lai), bạn có thể gặp khó khăn. Bạn sẽ phải thiết kế các paywall hỗ trợ phiên bản hiện tại (cũ) hoặc chấp nhận rằng người dùng với phiên bản hiện tại (cũ) có thể gặp sự cố với các paywall không được hiển thị. - **Mất khả năng targeting**: Tất cả người dùng sẽ thấy cùng một paywall được thiết kế cho đối tượng **All Users**, nghĩa là bạn mất đi khả năng targeting cá nhân hóa (bao gồm dựa trên quốc gia, attribution marketing hoặc các thuộc tính tùy chỉnh của riêng bạn). Nếu bạn chấp nhận những hạn chế này để đổi lấy tốc độ tải flow hoặc paywall nhanh hơn, hãy sử dụng phương thức `getFlowForDefaultAudience` như sau. Nếu không, hãy tiếp tục dùng `getFlow` đã được mô tả [ở trên](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder). ::: ```swift showLineNumbers Adapty.getFlowForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // the requested flow case let .failure(error): // handle the error } } ``` | Tham số | Bắt buộc | Mô tả | |---------|--------|-----------| | **placementId** | bắt buộc | Mã định danh của [Placement](placements). Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. | | **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 nếu thất bại. Chúng tôi khuyến nghị dùng tùy chọn này vì nó đảm bảo người dùng 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 thường xuyên gặp tình trạng mạng không ổn định, hãy cân nhắc 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 thời gian tải sẽ nhanh hơn bất kể kết nối mạng có yếu đến đâu. Cache được cập nhật thường xuyên nên hoàn toàn an toàn khi dùng trong phiên làm việc để tránh các yêu cầu mạng không cần thiết.
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 gỡ cài đặt ứng dụng hoặc xóa thủ công.
| ## Tùy chỉnh assets \{#customize-assets\} Để tùy chỉnh hình ảnh và video trong paywall/flow của bạn, hãy triển khai custom assets. Hình ảnh hero và video có ID được định 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 theo 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 ID tùy chỉnh](custom-media) trong Adapty dashboard. Ví dụ, bạn có thể: - Hiển thị hình ảnh hoặc video khác nhau cho một số người dùng. - Hiển thị hình ảnh xem trước cục bộ trong khi hình ảnh chính từ xa đang tải. - Hiển thị hình ảnh xem trước trước khi phát video. - Cung cấp độ phân giải pixel của video để trình phát có thể dành sẵn không gian bố cục (tỷ lệ khung hình = `width / height`) trước khi video tải. Truyền `nil` để bỏ qua phần này. Dưới đây là ví dụ về cách cung cấp các asset tùy chỉnh thông qua một dictionary đơn giản: ```swift showLineNumbers let customAssets: [String: AdaptyCustomAsset] = [ // Show a local image using a custom ID "custom_image": .image( .uiImage(value: UIImage(named: "image_name")!) ), // Show a local preview image while a remote main image is loading "hero_image": .image( .remote( url: URL(string: "https://example.com/image.jpg")!, preview: UIImage(named: "preview_image") ) ), // Show a local video with a preview image and a known resolution "hero_video": .video( .file( url: Bundle.main.url(forResource: "custom_video", withExtension: "mp4")!, preview: .uiImage(value: UIImage(named: "video_preview")!), resolution: CGSize(width: 1080, height: 1920) ) ), ] let flowConfig = try await AdaptyUI.getFlowConfiguration( forFlow: flow, assetsResolver: customAssets ) ``` :::note Nếu không tìm thấy asset, paywall/flow sẽ hiển thị theo giao diện mặc định. ::: ## Thiết lập timer do lập trình viên định nghĩa \{#set-up-developer-defined-timers\} Để sử dụng timer tùy chỉnh trong ứng dụng, hãy tạo một object tuân theo protocol `AdaptyTimerResolver`. Object này định nghĩa cách hiển thị từng timer tùy chỉnh. Nếu muốn, bạn có thể dùng trực tiếp dictionary `[String: Date]` vì nó đã tương thích với protocol này. Dưới đây là ví dụ: ```swift showLineNumbers @MainActor struct AdaptyTimerResolverImpl: AdaptyTimerResolver { func timerEndAtDate(for timerId: String) -> Date { switch timerId { case "CUSTOM_TIMER_6H": Date(timeIntervalSinceNow: 3600.0 * 6.0) // 6 hours case "CUSTOM_TIMER_NY": Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)) ?? Date(timeIntervalSinceNow: 3600.0) default: Date(timeIntervalSinceNow: 3600.0) // 1 hour } } } ``` Trong ví dụ này, `CUSTOM_TIMER_NY` và `CUSTOM_TIMER_6H` là các **Timer ID** của các timer do developer tự định nghĩa mà bạn đã thiết lập trong Adapty Dashboard. `timerResolver` đảm bảo ứng dụng của bạn cập nhật động từng 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 6 giờ bắt đầu từ khi người dùng mở paywall.tùy chọn
mặc định: `en`
|Mã định danh của [bản dịch paywall](add-paywall-locale-in-adapty-paywall-builder). Tham số này được kỳ vọng là mã ngôn ngữ gồm một hoặc hai thẻ con được phân cách bằng ký tự gạch ngang (**-**). Thẻ con đầu tiên là ngôn ngữ, thẻ con thứ hai là khu vực.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).
Xem [Bản dịch 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ố tải dữ liệu từ máy chủ và trả về dữ liệu đã cache nếu thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng 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 thường xuyên gặp tình trạng mạng không ổn định, hãy cân nhắc 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 sẽ tải nhanh hơn bất kể kết nối mạng có chập chờn đến đâu. Cache được cập nhật thường xuyên, nên hoàn toàn an toàn khi dùng trong phiên làm việc để tránh các yêu cầu mạng.
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 gỡ cài đặt ứng dụng hoặc thực hiện 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 để tải 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. 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, đồng thời đảm bảo độ tin cậy ngay cả khi kết nối mạng yếu.
| | **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ờ trễ hơn một chút so với giá trị đã chỉ định trong `loadTimeout`, do thao tác này 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ả | | :-------- | :---------- | | Paywall | Đối tượng [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall) chứa 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 cấu hình hiển thị 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 chưa được bật, cấu hình hiển thị sẽ không thể lấy được. ::: Sau khi lấy paywall, hãy kiểm tra xem nó có chứa cấu hình hiển thị hay không — đây là dấu hiệu cho thấy paywall được tạo bằng Paywall Builder. Điều này sẽ giúp bạn biết cách hiển thị paywall. Nếu cấu hình hiển thị có mặt, hãy xử lý nó như một paywall Paywall Builder; nếu không, [xử lý nó như một paywall Remote Config](present-remote-config-paywalls). Sử dụng phương thức `getPaywallConfiguration` để tải cấu hình view. ```swift showLineNumbers guard paywall.hasViewConfiguration else { // use your custom logic return } do { let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall, products: products ) // use loaded configuration } catch { // handle the error } ``` Tham số: | Tham số | Bắt buộc | Mô tả | | :----------------------- | :------------- | :---------- | | **paywall** | bắt buộc | Đố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 timeout cho phương thức này. Nếu hết thời gian chờ, dữ liệu được 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ể 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 request khác nhau bên dưới. | | **products** | tùy chọn | Cung cấp một mảng các đối tượng `AdaptyPaywallProduct` để tối ưu hóa thời điểm hiển thị sản phẩm trên màn hình. Nếu truyền vào `nil`, AdaptyUI sẽ tự động tải các sản phẩm cần thiết. | :::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 tải xong, hãy [hiển thị paywall](ios-present-paywalls). ## Lấy paywall cho đối tượng mặc định để tải nhanh hơn \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Thông thường, paywall được tải gần như ngay lập tức, nên bạn không cần lo lắng về việc tối ưu tốc độ. Tuy nhiên, trong trường hợp bạn có nhiều đối tượng và paywall, hoặc người dùng đang dùng kết nối internet yếu, việc tải paywall có thể mất nhiều thời gian hơn mong muốn. Trong những tình huống đó, 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 sẽ lấy paywall của placement đã chỉ định cho đối tượng **All Users**. Tuy nhiên, điều quan trọng cần hiểu là phương pháp được khuyến nghị vẫn là lấy paywall qua phương thức `getPaywall`, như đã trình bày chi tiết trong phần [Lấy thông tin Paywall](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) ở trên. :::warning Lý do chúng tôi khuyến nghị sử dụng `getPaywall` Phương thức `getPaywallForDefaultAudience` có một số hạn chế đáng kể: - **Vấn đề tương thích ngược**: Nếu bạn cần hiển thị các paywall khác nhau cho các phiên bản ứng dụng khác nhau (phiên bản hiện tại và tương lai), bạn có thể gặp khó khăn. Bạn sẽ phải thiết kế các paywall hỗ trợ phiên bản hiện tại (cũ) hoặc chấp nhận rằng người dùng với phiên bản hiện tại (cũ) có thể gặp sự cố với các paywall không hiển thị được. - **Mất khả năng nhắm mục tiêu**: Tất cả người dùng sẽ thấy cùng một paywall được thiết kế cho đối tượng **All Users**, nghĩa là bạn mất đi khả năng cá nhân hóa mục tiêu (bao gồm theo quốc gia, attribution marketing hoặc các thuộc tính tùy chỉnh của riêng bạn). Nếu bạn sẵn sàng chấp nhận những hạn chế này để có được tốc độ tải paywall nhanh hơn, hãy sử dụng phương thức `getPaywallForDefaultAudience` như sau. Nếu không, hãy dùng `getPaywall` đã được mô tả [ở trên](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder). ::: ```swift showLineNumbers Adapty.getPaywallForDefaultAudience(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` :::note Phương thức `getPaywallForDefaultAudience` khả dụng từ iOS SDK phiên bản 2.11.2 trở lên. ::: | Tham số | Bắt buộc | Mô tả | |---------|--------|-----------| | **placementId** | bắt buộc | Mã đị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`
|Mã định danh của [bản dịch paywall](add-remote-config-locale). Tham số này là mã ngôn ngữ gồm một hoặc nhiều thẻ con phân cá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 [Localizations 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.
| | **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 đã lưu trong bộ nhớ đệm nếu thất bại. Chúng tôi khuyến nghị lựa chọn này vì nó đảm bảo người dùng 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 thường xuyên gặp tình trạng kết nối không ổn định, hãy cân nhắc dùng `.returnCacheDataElseLoad` để trả về dữ liệu từ bộ nhớ đệm 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 thời gian tải sẽ nhanh hơn dù kết nối internet kém đến đâu. Bộ nhớ đệm được cập nhật thường xuyên, nên hoàn toàn an toàn khi sử dụng trong phiên làm việc để tránh các yêu cầu mạng.
Lưu ý rằng bộ nhớ đệm vẫn tồn tại sau khi khởi động lại ứng dụng và chỉ bị xóa khi cài đặt lại ứng dụng hoặc xóa thủ công.
| ## 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. Hero image và video có ID được định sẵn: `hero_image` và `hero_video`. Trong một custom asset bundle, bạn nhắm tới 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 ID tùy chỉnh](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 iOS SDK lên phiên bản 3.7.0 trở lên. ::: Đâ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: ```swift showLineNumbers let customAssets: [String: AdaptyCustomAsset] = [ // Show a local image using a custom ID "custom_image": .image( .uiImage(value: UIImage(named: "image_name")!) ), // Show a local preview image while a remote main image is loading "hero_image": .image( .remote( url: URL(string: "https://example.com/image.jpg")!, preview: UIImage(named: "preview_image") ) ), // Show a local video with a preview image "hero_video": .video( .file( url: Bundle.main.url(forResource: "custom_video", withExtension: "mp4")!, preview: .uiImage(value: UIImage(named: "video_preview")!) ) ), ] let paywallConfig = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall, assetsResolver: customAssets ) ``` :::note Nếu không tìm thấy asset, paywall sẽ trở về giao diện mặc định. ::: ## Thiết lập timer do developer định nghĩa \{#set-up-developer-defined-timers\} Để sử dụng custom timer trong ứng dụng mobile, hãy tạo một object tuân theo protocol `AdaptyTimerResolver`. Object này xác định cách mỗi custom timer sẽ được hiển thị. Nếu muốn, bạn có thể dùng trực tiếp dictionary `[String: Date]`, vì nó đã tuân thủ protocol này. Dưới đây là ví dụ: ```swift showLineNumbers @MainActor struct AdaptyTimerResolverImpl: AdaptyTimerResolver { func timerEndAtDate(for timerId: String) -> Date { switch timerId { case "CUSTOM_TIMER_6H": Date(timeIntervalSinceNow: 3600.0 * 6.0) // 6 hours case "CUSTOM_TIMER_NY": Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)) ?? Date(timeIntervalSinceNow: 3600.0) default: Date(timeIntervalSinceNow: 3600.0) // 1 hour } } } ``` Trong ví dụ này, `CUSTOM_TIMER_NY` và `CUSTOM_TIMER_6H` là **Timer ID** của các timer do developer tự định nghĩa, được thiết lập trong Adapty Dashboard. `timerResolver` đảm bảo ứng dụng của bạn cập nhật động từng 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ư ngày Tết Dương lịch. - `CUSTOM_TIMER_6H`: Thời gian còn lại trong khoảng 6 giờ bắt đầu từ khi người dùng mở paywall.
## Số lượt xem paywall quá lớn \{#the-paywall-view-number-is-too-big\}
**Sự cố**: Số lượt xem paywall hiển thị gấp đôi so với mong đợi.
**Nguyên nhân**: Bạn có thể đang gọi `logShowFlow` (iOS SDK v4+) / `logShowPaywall` trong code, khiến số lượt xem bị tính hai lần nếu bạn đang dùng Paywall Builder hoặc Flow Builder. Với các flow và paywall được xây dựng bằng những công cụ này, analytics được theo dõi tự động, nên bạn không cần gọi phương thức này.
**Giải pháp**: Đảm bảo bạn không gọi `logShowFlow` (iOS SDK v4+) / `logShowPaywall` trong code nếu đang sử dụng Paywall Builder hoặc Flow Builder.
## Các sự cố khác \{#other-issues\}
**Sự cố**: Bạn gặp phải các vấn đề liên quan đến Paywall Builder 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](ios-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: ios-present-paywall-builder-paywalls-in-observer-mode
---
---
title: "Hiển thị paywall Paywall Builder trong Observer mode trên iOS SDK"
description: "Tìm hiểu cách hiển thị paywall PB trong observer mode để có thêm thông tin chi tiết."
---
Nếu bạn đã tùy chỉnh paywall bằng Paywall Builder, bạn không cần phải 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 lẫn cách thức hiển thị.
:::warning
Phần này chỉ áp dụng cho [Observer mode](observer-vs-full-mode). Nếu bạn không làm việc trong Observer mode, hãy tham khảo [iOS - Hiển thị paywall Paywall Builder](ios-present-paywalls).
:::
Theo mặc định, SDK sẽ cố tải dữ liệu từ server và trả về dữ liệu đã cache nếu thất bại. Chúng tôi khuyến nghị phương án này vì nó đảm bảo người dùng 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 thường xuyên gặp 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ó trải nghiệm tải nhanh hơn, bất kể kết nối internet của họ có kém ổn định đến đâu. Cache được cập nhật thường xuyên, nên hoàn toàn an toàn khi sử dụng trong suốt phiên làm việc để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn được giữ nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt ứng dụng hoặc thực hiện dọn dẹp thủ công.
Adapty SDK lưu trữ flow và paywall 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 để tải flow và paywall nhanh hơn, cùng một server dự phòng độc lập trong trường hợp CDN không thể truy cập được.
| | **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`, do thao tác có thể bao gồm nhiều yêu cầu khác nhau bên dưới.
| :::note Trong v4, tham số `locale` đã được chuyển ra khỏi `getFlow` và sang `getFlowConfiguration` (chỉ dùng khi render với AdaptyUI). Đối với các paywall tùy chỉnh, tất cả các locale khả dụng được trả về cùng nhau trong `flow.remoteConfigs` — hãy chọn locale phù hợp với thiết bị của người dùng hoặc cài đặt trong ứng dụng của bạn. ::: Đừng hardcode ID sản phẩm! Vì các flow đượ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 nên hiển thị đúng 2 sản phẩm đó. Nhưng nếu sau này lấy được 3 sản phẩm, ứng dụng phải hiển thị cả 3 mà không cần thay đổi code. Thứ duy nhất bạn cần hardcode là placement ID. Các tham số phản hồi: | Tham số | Mô tả | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Một đối tượng `AdaptyFlow` chứa placement, các định danh (`id`, `variationId`), tên, mảng `remoteConfigs` (một mục cho mỗi ngôn ngữ được cấu hình) và cờ `hasViewConfiguration`. Để lấy sản phẩm cho flow, hãy gọi `getPaywallProducts(flow:)`. | ## Lấy sản phẩm \{#fetch-products\} Sau khi có flow, bạn có thể truy vấn mảng sản phẩm tương ứng với nó:Theo mặc định, SDK sẽ cố tải dữ liệu từ máy chủ và trả về dữ liệu đã cache nếu có lỗi xảy ra. Chúng tôi khuyến nghị dùng tùy chọn này vì nó đảm bảo người dùng 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 thường xuyên gặp tình trạng mạng không ổn định, hãy cân nhắc 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 sẽ có tốc độ tải nhanh hơn bất kể kết nối mạng của họ kém đến đâu. Cache được cập nhật thường xuyên, vì vậy hoàn toàn an toàn khi dùng trong suốt phiên làm việc để tránh các yêu cầu mạng không cần thiết.
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 gỡ cài đặt ứng dụng hoặc xóa thủ công.
|tùy chọn
mặc định: `en`
|Định danh của [bản dịch paywall](add-remote-config-locale). Tham số này 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à mã ngôn ngữ, thẻ con thứ hai là mã vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).
Xem [Localizations and locale codes](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ừ máy chủ và trả về dữ liệu đã cache nếu thất bại. Chúng tôi khuyến nghị phương án này vì nó đảm bảo người dùng 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 thường gặp tình trạng kết nối internet không ổn định, hãy cân nhắc 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 thời gian tải sẽ nhanh hơn bất kể chất lượng kết nối. Cache được cập nhật thường xuyên nên hoàn toàn an toàn khi dùng trong phiên làm việc để tránh các yêu cầu mạng không cần thiết.
Lưu ý rằng cache vẫn được giữ nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi cài đặt lại ứng dụng hoặc thực hiện dọn dẹp thủ công.
Adapty SDK lưu trữ paywall ở 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 để tải 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. 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 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`, do thao tác có thể bao gồm nhiều yêu cầu khác nhau bên dưới.
| Đừng hardcode product ID! Vì 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 (chẳng hạn 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 những trường hợp này. Ví dụ: nếu ban đầu bạn lấy được 2 sản phẩm, ứng dụng nên hiển thị đúng 2 sản phẩm đó. Nhưng nếu sau này lấy được 3 sản phẩm, ứng dụng cũng phải hiển thị cả 3 mà không cần thay đổi code. Thứ duy nhất bạn cần hardcode là placement ID. Tham số trả về: | Tham số | Mô tả | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Một đối tượng [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall) 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 paywall đó: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` nghĩa là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha của Brazil.
Xem [Localizations and locale codes](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.
| | **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 đã lưu trong cache nếu 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 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 thường xuyên gặp tình trạng internet không ổn định, hãy cân nhắc 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 thời gian tải sẽ nhanh hơn dù kết nối internet kém ổn định. Cache được cập nhật thường xuyên, vì vậy hoàn toàn an toàn khi sử dụng trong phiên làm việc để tránh các yêu cầu mạng không cần thiết.
Lưu ý rằng cache vẫn được giữ nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt ứng dụng hoặc thực hiện dọn dẹp thủ công.
|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://swift.adapty.io/documentation/adapty/adaptyprofile) cung cấp thông tin toàn diện về các mức độ truy cập, gói đăng ký và 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ợ. ::: ## In-app purchase từ App Store \{#in-app-purchases-from-the-app-store\} Khi người dùng bắt đầu mua hàng trong App Store và giao dịch được chuyển sang ứng dụng của bạn, bạn có hai lựa chọn: - **Xử lý giao dịch ngay lập tức:** Trả về `true` trong `shouldAddStorePayment`. Thao tác này sẽ hiển thị màn hình mua hàng của Apple ngay lập tức. - **Lưu đối tượng sản phẩm để xử lý sau:** Trả về `false` trong `shouldAddStorePayment`, sau đó gọi `makePurchase` với sản phẩm đã lưu vào lúc thích hợp. Điều này có thể hữu ích nếu bạn cần hiển thị nội dung tùy chỉnh cho người dùng trước khi kích hoạt giao dịch mua hàng. Đây là đoạn code hoàn chỉnh: ```swift showLineNumbers title="Swift" final class YourAdaptyDelegateImplementation: AdaptyDelegate { nonisolated func shouldAddStorePayment(for product: AdaptyDeferredProduct) -> Bool { // 1a. // Return `true` to continue the transaction in your app. The Apple purchase system screen will show automatically. // 1b. // Store the product object and return `false` to defer or cancel the transaction. false } // 2. Continue the deferred purchase later on by passing the product to `makePurchase` when the timing is appropriate func continueDeferredPurchase() async { let storedProduct: AdaptyDeferredProduct = // get the product object from 1b. do { try await Adapty.makePurchase(product: storedProduct) } catch { // handle the error } } } ``` ## Đổi mã ưu đãi trên iOS \{#redeem-offer-codes-in-ios\} --- no_index: true --- import Callout from '../../../components/Callout.astro';Một đối tượng [`AdaptyProfile`](https://swift.adapty.io/documentation/adapty/adaptyprofile). Model này chứa thông tin về mức độ truy cập, các 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: ios-transaction-management --- --- title: "Quản lý giao dịch nâng cao trong iOS SDK" description: "Hoàn tất giao dịch thủ công trong ứng dụng iOS của bạn với Adapty SDK." --- :::note Quản lý giao dịch nâng cao được hỗ trợ trong Adapty iOS SDK bắt đầu từ phiên bản 3.12. ::: Quản lý giao dịch nâng cao trong Adapty cho phép bạn kiểm soát nhiều hơn cách các giao dịch được xử lý, xác minh và hoàn tất. Tính năng này giới thiệu ba tính năng tùy chọn hoạt động cùng nhau: | Tính năng | Mục đích | |-------------------------------------------------------------|----------| | [`appAccountToken`](#assign-appaccounttoken) | Liên kết giao dịch Apple với ID người dùng nội bộ của bạn | | [`jwsTransaction`](#access-the-jws-representation) | Cung cấp payload giao dịch đã ký của Apple để xác thực | | [Hoàn tất thủ công](#control-transaction-finishing-behavior) | Cho phép bạn hoàn tất giao dịch chỉ sau khi backend xác nhận thành công | Kết hợp lại, các công cụ này giúp bạn xây dựng quy trình xác thực tùy chỉnh mạnh mẽ trong khi Adapty vẫn tiếp tục đồng bộ giao dịch với backend của mình. :::important Hầu hết ứng dụng không cần đến tính năng này. Mặc định, Adapty tự động xác thực và hoàn tất các giao dịch StoreKit. Chỉ sử dụng hướng dẫn này nếu bạn chạy xác thực backend riêng hoặc muốn kiểm soát hoàn toàn vòng đời mua hàng. ::: ## Gán `appAccountToken` \{#assign-appaccounttoken\} [`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, giúp backend của bạn có thể khớp dữ liệu App Store với người dùng của bạn. 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ị. Đ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 khởi tạo 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 chỉ truyền token, nó sẽ không được đưa vào giao dịch. :::Với StoreKit 1: đối tượng [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).
Với StoreKit 2: đối tượng [Transaction](https://developer.apple.com/documentation/storekit/transaction).
|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ể thiết lập 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, chúng có thể là số buổi tập mỗi tuần; với ứng dụng học ngoại ngữ, chúng có thể là trình độ kiến thức 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 sử 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. ```swift showLineNumbers do { builder = try builder.with(customAttribute: "value1", forKey: "key1") } catch { // handle key/value validation error } ``` Để xóa khóa hiện có, hãy sử dụng phương thức `.withRemoved(customAttributeForKey:)`: ```swift showLineNumbers do { builder = try builder.withRemoved(customAttributeForKey: "key2") } catch { // handle error } ``` Đôi khi bạn cần biết những thuộc tính tùy chỉnh nào đã được cài đặt trước đó. Để làm điều này, hãy sử 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 cập nhật kịp thời, vì thuộc tính người dùng có thể được gửi từ nhiều thiết bị khác nhau vào bất kỳ lúc nào, nên các thuộc tính trên máy chủ có thể đã thay đổi sau lần đồng bộ 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 khóa tối đa 30 ký tự. Tên khóa có thể 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 tối đa 50 ký tự. --- # File: subscription-status --- --- title: "Kiểm tra trạng thái gói đăng ký trong iOS SDK" description: "Theo dõi và quản lý trạng thái gói đăng ký của người dùng trong Adapty để cải thiện khả năng giữ chân khách hàng." --- 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 nhúng thủ công các product ID 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ý, hãy thiết lập [App Store Server Notifications](enable-app-store-server-notifications). ## 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://swift.adapty.io/documentation/adapty/adaptyprofile). 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](identifying-users#set-customer-user-id-on-configuration), rồi cập nhật lại mỗi khi có thay đổi. Như vậy, bạn có thể sử dụng đối tượng profile mà không cần phải gọi lại nhiều lần. Để nhận thông báo khi profile được cập nhật, hãy lắng nghe các thay đổi của profile như mô tả trong phần [Lắng nghe cập nhật profile, bao gồm mức độ truy cập](subscription-status#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ừ server \{#retrieving-the-access-level-from-the-server\} Để lấy mức độ truy cập từ server, sử dụng phương thức `.getProfile()`:Đối tượng [AdaptyProfile](https://swift.adapty.io/documentation/adapty/adaptyprofile). Thông thường, bạn chỉ cần kiểm tra trạng thái mức độ truy cập của profile để xác định xem người dùng có quyền truy cập premium vào ứng dụng hay không.
Phương thức `.getProfile` luôn cố gắng truy vấn API nên cho kết quả cập nhật nhất. 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ừ server thì dữ liệu trong cache sẽ được trả về. Cũng cần lưu ý rằng Adapty SDK cập nhật cache của `AdaptyProfile` định kỳ để giữ thông tin luôn được đồng bộ nhất có thể.
| Phương thức `.getProfile()` trả về hồ sơ người dùng, từ đó bạn có thể lấy trạng thái mức độ truy cập. Một ứng dụng có thể có nhiều mức độ truy cập. Ví dụ: nếu bạn có ứng dụng đọc báo và bán gói đăng ký theo từng chủ đề riêng biệt, 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 đó chỉ cần dùng mức mặc định "premium" là đủ. Dưới đây là ví dụ kiểm tra mức độ truy cập "premium" mặc định:tùy chọn
mặc định: `en`
|Định danh của bản dịch onboarding. Tham số này được kỳ vọng là một mã ngôn ngữ gồm một hoặc hai subtag được phân tách bằng dấu trừ (**-**). Subtag đầu tiên là ngôn ngữ, subtag 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ã 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ố 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 thường xuyên gặp 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ọ yếu đến mức nào. Cache được cập nhật thường xuyên, vì vậy an toàn khi sử dụng trong phiên làm việc để tránh các yêu cầu mạng.
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ữ onboarding cục bộ theo 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 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 onboarding 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 timeout cho phương thức này. Nếu timeout được đạt tới, 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ể 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://swift.adapty.io/documentation/adapty/adaptyonboarding) với: định danh và cấu hình onboarding, Remote Config, và một số thuộc tính khác. | ## 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 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. Để giải quyết vấn đề 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ể gây ra sự cố khi hỗ trợ nhiều phiên bản ứng dụng, đòi hỏi phả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ỏ khả năng nhắm mục tiêu theo quốc gia, attribution hoặc thuộc tính tùy chỉnh. Nếu tốc độ lấy nhanh hơn vượt trội hơn những hạn chế này với trường hợp sử dụng của bạn, hãy dùng `getOnboardingForDefaultAudience` như hiển thị bên dưới. Nếu không, hãy sử dụng `getOnboarding` như được mô tả [ở trên](#fetch-onboarding). ::: ```swift showLineNumbers Adapty.getOnboardingForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(onboarding): // the requested onboarding case let .failure(error): // handle the error } } ``` 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 dịch onboarding. Tham số này được kỳ vọng là một mã ngôn ngữ gồm một hoặc hai subtag được phân tách bằng dấu trừ (**-**). Subtag đầu tiên là ngôn ngữ, subtag 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ã 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ố 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 thường xuyên gặp 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ọ yếu đến mức nào. Cache được cập nhật thường xuyên, vì vậy an toàn khi sử dụng trong phiên làm việc để tránh các yêu cầu mạng.
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ữ onboarding cục bộ theo 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 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 onboarding trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet kém.
| --- # File: ios-present-onboardings --- --- title: "Hiển thị onboarding trong iOS SDK" description: "Khám phá cách hiển thị onboarding trên iOS để tăng tỷ lệ chuyển đổi và doanh thu." --- :::tip **Từ SDK v4 (beta)**, bạn có thể xây dựng [flow](get-pb-paywalls) như một giải pháp mạnh mẽ hơn so với onboarding. Khác với onboarding chạy bên trong WebView, flow render trực tiếp trên thiết bị — mang lại animation mượt mà hơn, giao diện iOS nhất quán, tốc độ tải nhanh hơn và không phụ thuộc vào WebView runtime. Xem [Lấy flow & paywall](get-pb-paywalls) và [Hiển thị flow & paywall](ios-present-paywalls) để bắt đầu. ::: 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 ứng dụng để hiển thị cho người dùng. Onboarding đó đã bao gồm cả nội dung cần hiển thị lẫn 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 iOS SDK](sdk-installation-ios) phiên bản 3.8.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 bằng Swift \{#present-onboardings-in-swift\} Để hiển thị onboarding trực quan trên màn hình thiết bị, hãy thực hiện các bước sau: 1. Lấy cấu hình view onboarding bằng phương thức `.getOnboardingConfiguration`. 2. Khởi tạo onboarding trực quan mà bạn muốn hiển thị bằng phương thức `.onboardingController`: Tham số đầu vào: | Tham số | Bắt buộc | Mô tả | |:------------------------------|:---------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onboarding configuration** | bắt buộc | Một đối tượng `AdaptyUI.OnboardingConfiguration` chứa tất cả các thuộc tính của onboarding. Dùng phương thức `AdaptyUI.getOnboardingConfiguration` để lấy nó. | | **delegate** | bắt buộc | Một `AdaptyOnboardingControllerDelegate` để lắng nghe các sự kiện của onboarding. | Kết quả trả về: | Đối tượng | Mô tả | |:-------------------------------|:--------------------------------------------------------| | **AdaptyOnboardingController** | Một đối tượng đại diện cho màn hình onboarding được yêu cầu | 3. Sau khi đối tượng được tạo thành công, bạn có thể hiển thị nó trên màn hình thiết bị: ```swift showLineNumbers title="Swift" import Adapty import AdaptyUI // 0. Get an onboarding if you haven't done it yet let onboarding = try await Adapty.getOnboarding(placementId: "YOUR_PLACEMENT_ID") // 1. Obtain the onboarding view configuration: let configuration = try AdaptyUI.getOnboardingConfiguration(forOnboarding: onboarding) // 2. Create Onboarding View Controller let onboardingController = try AdaptyUI.onboardingController( with: configuration, delegate:
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ụ: khi 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 delegate `onboardingController` sẽ được kích hoạt với case `.custom(id:)` và tham số `actionId` chính là **Action ID** từ builder. Bạn có thể tự tạo ID của riêng mình, chẳng hạn như "allowNotifications".
```swift showLineNumbers
func onboardingController(_ controller: AdaptyOnboardingController, onCustomAction action: AdaptyOnboardingsCustomAction) {
if action.actionId == "allowNotifications" {
// Request notification permissions
}
}
func onboardingController(_ controller: AdaptyOnboardingController, didFailWithError error: AdaptyUIError) {
// Handle errors
}
```
:::important
Lưu ý rằng bạn cần tự xử lý những gì xảy ra khi người dùng đóng onboarding. Ví dụ: bạn cần dừng hiển thị onboarding đó.
:::
Ví dụ:
```swift showLineNumbers
func onboardingController(_ controller: AdaptyOnboardingController, onCloseAction action: AdaptyOnboardingsCloseAction) {
controller.dismiss(animated: true)
}
```
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 mục **Subscriptions**.
3. Đảm bảo sản phẩm bạn đang kiểm tra được đánh dấu là **Ready to Submit**. Nếu chưa, hãy làm theo hướng dẫn trên trang [Product in App Store](app-store-products).
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 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ở lại mục **Subscriptions**.
2. Nhấp vào tên nhóm gói đăng ký để xem các sản phẩm.
3. Chọn sản phẩm bạn đang kiểm tra.
4. Cuộn xuống mục **Availability** và kiểm tra rằng tất cả các quốc gia và khu vực cần thiết đều được liệt kê.
## Bước 4. Kiểm tra giá sản phẩm \{#step-5-check-product-prices\}
1. Quay lại mục **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 **Subscription Pricing** và mở rộng mục **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 thanh toán, tài khoản ngân hàng và biểu mẫu thuế còn hiệu lực \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\}
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 rằng **Paid Apps Agreement**, **Bank Account** và **Tax forms** đều hiển thị trạng thái **Active**.
Bằng cách làm theo các bước trên, bạn sẽ có thể khắc phục cảnh báo `InvalidProductIdentifiers` và đưa sản phẩm lên cửa hàng.
## Bước 6. Tạo lại sản phẩm nếu bị kẹt \{#step-6-recreate-the-product-if-its-stuck\}
Các bước 1–5 có thể đều vượt qua — trạng thái `Approved`, Bundle ID khớp, API key hợp lệ — nhưng SDK vẫn trả về lỗi `1000 noProductIDsFound`. Trong trường hợp đó, sản phẩm có thể đang 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 hiển thị qua đường dẫn 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 ID sản phẩm đó. Sau khi tạo lại, hãy chờ tối đa 24 giờ để thay đổi được áp dụng.
---
# File: cantMakePayments
---
---
title: "Khắc phục lỗi Code-1003 cantMakePayment"
description: "Giải quyết lỗi 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-ios-sdk-v4
---
---
title: "Migrate Adapty iOS SDK sang v4.0"
description: "Migrate sang Adapty iOS SDK v4.0 (beta) bằng cách thay thế các paywall API bằng flow API, tương thích với cả Flow Builder và Paywall Builder."
---
Adapty iOS SDK 4.0 (beta) giới thiệu flow và đổi tên các paywall API cho phù hợp. Các API mới hoạt động với cả Flow Builder mới và Paywall Builder hiện có — không cần thay đổi cấu hình trên Adapty Dashboard.
## Tham chiếu nhanh \{#quick-reference\}
| v3 | v4 |
|---|---|
| `Adapty.getPaywall(placementId:locale:)` | `Adapty.getFlow(placementId:)` |
| `AdaptyUI.getPaywallConfiguration(forPaywall:)` | `AdaptyUI.getFlowConfiguration(forFlow:locale:)` |
| `Adapty.getPaywallProducts(paywall:)` | `Adapty.getPaywallProducts(flow:)` |
| `Adapty.logShowPaywall(_:)` | `Adapty.logShowFlow(_:)` |
| `AdaptyPaywallController` | `AdaptyFlowController` |
| `AdaptyPaywallControllerDelegate` | `AdaptyFlowControllerDelegate` |
| `AdaptyUI.paywallController(with:delegate:)` | `AdaptyUI.flowController(with:delegate:)` |
| `.paywall()` (SwiftUI modifier) | `.flow()` |
| `AdaptyPaywallView` | `AdaptyFlowView` |
| `didFailRenderingWith:` / `didFailRendering:` | `didReceiveError:` |
| `Adapty.updateAttribution(_:source:)` (`source: String`) | `Adapty.updateAttribution(_:source:)` (`source: AdaptyAttributionSource`) |
| `Adapty.setIntegrationIdentifier(key:value:)` | `Adapty.setIntegrationIdentifier(_:)` (`AdaptyIntegrationIdentifier`) |
## Phiên bản iOS tối thiểu \{#minimum-ios-version\}
Adapty iOS SDK 4.0 nâng deployment target tối thiểu từ iOS 13.0 lên **iOS 15.0**. Hãy đặt iOS Deployment Target của dự án lên 15.0 hoặc cao hơn trước khi nâng cấp.
## Cài đặt: CocoaPods không còn được hỗ trợ \{#installation-cocoapods-no-longer-supported\}
Adapty iOS SDK 4.0 bỏ hỗ trợ CocoaPods. Hãy cài đặt SDK bằng [Swift Package Manager](sdk-installation-ios#install-adapty-sdk).
Nếu dự án của bạn vẫn đang dùng CocoaPods, hãy xóa các pod `Adapty` và `AdaptyUI` khỏi `Podfile`, chạy `pod install` để dọn sạch, rồi thêm package trong Xcode qua **File → Add Package Dependency** với URL `https://github.com/adaptyteam/AdaptySDK-iOS.git`.
## Các API đã bị xóa \{#removed-apis\}
- **`Adapty.getPaywallProductsWithoutDeterminingOffer(paywall:)`** — đã xóa. Tất cả sản phẩm giờ đây đã bao gồm thông tin ưu đãi, nên bước kiểm tra tính đủ điều kiện riêng biệt không còn cần thiết nữa.
- **`AdaptyPaywallProductWithoutDeterminingOffer`** — đã xóa. Các callback trước đây nhận kiểu này (chẳng hạn `didSelectProduct`) giờ nhận `AdaptyPaywallProduct`.
## Tạm thời xóa tính năng mua in-app purchase được quảng bá trên App Store \{#app-store-promoted-in-app-purchases-temporarily-removed\}
Trong quá trình migrate sang StoreKit 2, Adapty iOS SDK 4.0 xóa hỗ trợ cho in-app purchase được quảng bá trên App Store. Phương thức delegate `shouldAddStorePayment(for:)` và kiểu `AdaptyDeferredProduct` mà nó nhận đều không khả dụng trong phiên bản 4.0.
:::warning
Việc xóa này chỉ là tạm thời — hỗ trợ in-app purchase được quảng bá sẽ quay trở lại trong một bản phát hành 4.x tiếp theo. Nếu ứng dụng của bạn phụ thuộc vào in-app purchase được quảng bá, hãy tiếp tục dùng iOS SDK 3.x cho đến khi tính năng này được khôi phục.
:::
## Lấy paywall \{#fetching-paywalls\}
### getPaywall + getPaywallConfiguration → getFlow + getFlowConfiguration \{#getpaywall--getpaywallconfiguration--getflow--getflowconfiguration\}
Các kiểu trả về thay đổi từ `AdaptyPaywall` / `AdaptyUI.PaywallConfiguration` sang `AdaptyFlow` / `AdaptyUI.FlowConfiguration`. Tham số `locale` được chuyển ra khỏi lệnh gọi fetch và sang `getFlowConfiguration`:
```diff showLineNumbers
- let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en")
- let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration(forPaywall: paywall)
+ let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID")
+ let flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow, locale: "en")
```
### getPaywallProducts(paywall:) → getPaywallProducts(flow:) \{#getpaywallproductspaywall--getpaywallproductsflow\}
`getPaywallProducts` giờ nhận `AdaptyFlow` được trả về bởi `Adapty.getFlow`:
```diff showLineNumbers
- let products = try await Adapty.getPaywallProducts(paywall: paywall)
+ let products = try await Adapty.getPaywallProducts(flow: flow)
```
## Theo dõi lượt xem paywall \{#tracking-paywall-views\}
### logShowPaywall(_:) → logShowFlow(_:) \{#logshowpaywall_--logshowflow_\}
`logShowPaywall` được đổi tên thành `logShowFlow` và giờ nhận `AdaptyFlow` thay vì `AdaptyPaywall`. Sự kiện vẫn được ghi lại theo cùng một biến thể, vì vậy các chỉ số funnel và A/B test hiện có vẫn hoạt động bình thường mà không cần thay đổi trên dashboard.
```diff showLineNumbers
- try await Adapty.logShowPaywall(paywall)
+ try await Adapty.logShowFlow(flow)
```
Như trong v3, bạn không cần gọi phương thức này khi hiển thị flow hoặc paywall được render bởi [Flow Builder](adapty-flow-builder) hoặc [Paywall Builder](adapty-paywall-builder) — Adapty tự động theo dõi các lượt xem đó.
## UIKit \{#uikit\}
### AdaptyPaywallController → AdaptyFlowController \{#adaptypaywall controller--adaptyflo wcontroller\}
Đổi tên kiểu controller và factory method:
```diff showLineNumbers
- let controller = try AdaptyUI.paywallController(
- with: paywallConfiguration,
- delegate: self
- )
+ let controller = try AdaptyUI.flowController(
+ with: flowConfiguration,
+ delegate: self
+ )
```
### AdaptyPaywallControllerDelegate → AdaptyFlowControllerDelegate \{#adaptypaywall controllerdelegate--adaptyflo wcontrollerdelegate\}
Đổi tên protocol và cập nhật mọi chữ ký phương thức. Lưu ý rằng `didSelectProduct` giờ nhận `AdaptyPaywallProduct` thay vì `AdaptyPaywallProductWithoutDeterminingOffer` đã bị xóa.
```diff showLineNumbers
- class YourClass: AdaptyPaywallControllerDelegate {
+ class YourClass: AdaptyFlowControllerDelegate {
- func paywallControllerDidAppear(_ controller: AdaptyPaywallController) { }
+ func flowControllerDidAppear(_ controller: AdaptyFlowController) { }
- func paywallControllerDidDisappear(_ controller: AdaptyPaywallController) { }
+ func flowControllerDidDisappear(_ controller: AdaptyFlowController) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didPerform action: AdaptyUI.Action) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didPerform action: AdaptyUI.Action) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didSelectProduct product: AdaptyPaywallProduct) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didStartPurchase product: AdaptyPaywallProduct) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didStartPurchase product: AdaptyPaywallProduct) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFinishPurchase product: AdaptyPaywallProduct,
- purchaseResult: AdaptyPurchaseResult) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFinishPurchase product: AdaptyPaywallProduct,
+ purchaseResult: AdaptyPurchaseResult) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFailPurchase product: AdaptyPaywallProduct,
- error: AdaptyError) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFailPurchase product: AdaptyPaywallProduct,
+ error: AdaptyError) { }
- func paywallControllerDidStartRestore(_ controller: AdaptyPaywallController) { }
+ func flowControllerDidStartRestore(_ controller: AdaptyFlowController) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFinishRestoreWith profile: AdaptyProfile) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFinishRestoreWith profile: AdaptyProfile) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFailRestoreWith error: AdaptyError) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFailRestoreWith error: AdaptyError) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFailRenderingWith error: AdaptyUIError) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didReceiveError error: AdaptyUIError) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFailLoadingProductsWith error: AdaptyError) -> Bool { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFailLoadingProductsWith error: AdaptyError) -> Bool { }
- func paywallController(_ controller: AdaptyPaywallController,
- didPartiallyLoadProducts failedIds: [String]) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didPartiallyLoadProducts failedIds: [String]) { }
- func paywallController(_ controller: AdaptyPaywallController,
- didFinishWebPaymentNavigation product: AdaptyPaywallProduct?,
- error: AdaptyError?) { }
+ func flowController(_ controller: AdaptyFlowController,
+ didFinishWebPaymentNavigation product: AdaptyPaywallProduct?,
+ error: AdaptyError?) { }
}
```
## SwiftUI \{#swiftui\}
### Modifier .paywall() → .flow() \{#paywall-modifier--flow\}
Đổi tên modifier và cập nhật tên tham số cấu hình:
```diff showLineNumbers
@State var flowPresented = false // đổi tên tùy ý — tên biến là do bạn chọn
var body: some View {
Text("Hello, AdaptyUI!")
- .paywall(
+ .flow(
isPresented: $flowPresented,
- paywallConfiguration: paywallConfiguration,
+ flowConfiguration: flowConfiguration,
didFailPurchase: { product, error in /* handle the error */ },
didFinishRestore: { profile in /* check access level and dismiss */ },
didFailRestore: { error in /* handle the error */ },
- didFailRendering: { error in flowPresented = false }
+ didReceiveError: { error in flowPresented = false }
)
}
```
Callback được đổi tên này kích hoạt cho các lỗi render giống như `didFailRendering` trước đây, cộng thêm các lỗi runtime mới từ flow script (JavaScript exception trên `AdaptyUIError` code `4105` — `.jsException`). Phần thân handler hiện có không cần thay đổi code — chỉ cần đổi tên tham số.
### AdaptyPaywallView → AdaptyFlowView \{#adaptypaywall view--adaptyflo wview\}
Đổi tên view, cập nhật tham số cấu hình, và cập nhật bất kỳ closure `didSelectProduct` nào — giờ nó nhận `AdaptyPaywallProduct` thay vì `AdaptyPaywallProductWithoutDeterminingOffer` đã bị xóa:
```diff showLineNumbers
- AdaptyPaywallView(
- paywallConfiguration: paywallConfiguration,
- didSelectProduct: { product: AdaptyPaywallProductWithoutDeterminingOffer in /* handle */ },
+ AdaptyFlowView(
+ flowConfiguration: flowConfiguration,
+ didSelectProduct: { product: AdaptyPaywallProduct in /* handle */ },
didFailPurchase: { product, error in /* handle the error */ },
didFinishRestore: { profile in /* check access level and dismiss */ },
didFailRestore: { error in /* handle the error */ },
- didFailRendering: { error in /* handle the error */ }
+ didReceiveError: { error in /* handle the error */ }
)
```
## Asset tùy chỉnh của AdaptyUI \{#adaptyui-custom-assets\}
### AdaptyUICustomVideoAsset \{#adaptyuicustomvideoasset\}
Có hai thay đổi ảnh hưởng đến mọi call site hiện có:
- `.player` giờ nhận `AVPlayer` thay vì `AVQueuePlayer`.
- Mỗi case được thêm tham số cuối `resolution: CGSize?`. Truyền `nil` để giữ nguyên hành vi hiện tại, hoặc truyền kích thước pixel thực tế để player có thể dành trước không gian layout (tỷ lệ khung hình = `width / height`) trước khi video tải xong.
```diff showLineNumbers
- case file(url: URL, preview: AdaptyUICustomImageAsset?)
- case remote(url: URL, preview: AdaptyUICustomImageAsset?)
- case player(item: AVPlayerItem, player: AVQueuePlayer, preview: AdaptyUICustomImageAsset?)
+ case file(url: URL, preview: AdaptyUICustomImageAsset?, resolution: CGSize?)
+ case remote(url: URL, preview: AdaptyUICustomImageAsset?, resolution: CGSize?)
+ case player(item: AVPlayerItem, player: AVPlayer, preview: AdaptyUICustomImageAsset?, resolution: CGSize?)
```
## Attribution và integration identifier \{#attribution-and-integration-identifiers\}
### updateAttribution(_:source:) \{#updateattribution_source\}
Tham số `source` thay đổi từ `String` sang kiểu `AdaptyAttributionSource` mới, và `AdaptyProfile.AttributionSource` trước đây được lồng bên trong giờ được đổi tên thành `AdaptyAttributionSource` ở cấp độ top-level. Sử dụng một trong các source được định nghĩa sẵn, hoặc truyền string literal cho bất kỳ source nào khác — `AdaptyAttributionSource` tuân thủ `ExpressibleByStringLiteral`, nên các lệnh gọi dùng string literal hiện có vẫn biên dịch được.
```diff showLineNumbers
- try await Adapty.updateAttribution(attribution, source: "adjust")
+ try await Adapty.updateAttribution(attribution, source: .adjust)
```
Các source được định nghĩa sẵn: `.appleAds`, `.adjust`, `.appsflyer`, `.branch`, `.tenjin`. Nếu bạn lưu source trong biến `String`, hãy wrap nó lại: `AdaptyAttributionSource(rawValue: yourSource)`.
### setIntegrationIdentifier(_:) \{#setintegrationidentifier_\}
`setIntegrationIdentifier(key:value:)` được thay thế bằng một phương thức variadic nhận một hoặc nhiều giá trị `AdaptyIntegrationIdentifier`. Sử dụng các factory method được định nghĩa sẵn thay vì raw string key:
```diff showLineNumbers
- try await Adapty.setIntegrationIdentifier(key: "appsflyer_id", value: uid)
+ try await Adapty.setIntegrationIdentifier(.appsflyerId(uid))
```
Bạn có thể đặt nhiều identifier trong một lần gọi:
```swift showLineNumbers
try await Adapty.setIntegrationIdentifier(
.appsflyerId(uid),
.adjustDeviceId(adid)
)
```
Thay thế mỗi chuỗi key cũ bằng factory method tương ứng:
| Key v3 | Factory v4 |
|---|---|
| `"adjust_device_id"` | `.adjustDeviceId(_:)` |
| `"airbridge_device_id"` | `.airbridgeDeviceId(_:)` |
| `"amplitude_user_id"` | `.amplitudeUserId(_:)` |
| `"amplitude_device_id"` | `.amplitudeDeviceId(_:)` |
| `"appmetrica_device_id"` | `.appmetricaDeviceId(_:)` |
| `"appmetrica_profile_id"` | `.appmetricaProfileId(_:)` |
| `"appsflyer_id"` | `.appsflyerId(_:)` |
| `"branch_id"` | `.branchId(_:)` |
| `"facebook_anonymous_id"` | `.facebookAnonymousId(_:)` |
| `"firebase_app_instance_id"` | `.firebaseAppInstanceId(_:)` |
| `"mixpanel_user_id"` | `.mixpanelUserId(_:)` |
| `"one_signal_subscription_id"` | `.oneSignalSubscriptionId(_:)` |
| `"one_signal_player_id"` | `.oneSignalPlayerId(_:)` |
| `"posthog_distinct_user_id"` | `.posthogDistinctUserId(_:)` |
| `"pushwoosh_hwid"` | `.pushwooshHWID(_:)` |
| `"tenjin_analytics_installation_id"` | `.tenjinAnalyticsInstallationId(_:)` |
---
# File: migration-to-ios-315
---
---
title: "Migrate Adapty iOS SDK to v3.15"
description: "Migrate to Adapty iOS SDK v3.15 for better performance and new monetization features."
---
Nếu bạn đang dùng [Paywall Builder](adapty-paywall-builder) trong [Observer mode](observer-vs-full-mode), bắt đầu từ iOS SDK 3.15, bạn cần triển khai một phương thức mới là `observerModeDidInitiateRestorePurchases(onStartRestore:onFinishRestore:)`. Phương thức này cung cấp khả năng kiểm soát chi tiết hơn đối với logic khôi phục, cho phép bạn xử lý việc khôi phục giao dịch mua trong flow tùy chỉnh của mình. Để biết chi tiết triển khai đầy đủ, tham khảo [Hiển thị paywall Paywall Builder trong Observer mode](ios-present-paywall-builder-paywalls-in-observer-mode).
```diff showLineNumbers
func observerMode(didInitiatePurchase product: AdaptyPaywallProduct,
onStartPurchase: @escaping () -> Void,
onFinishPurchase: @escaping () -> Void) {
// use the product object to handle the purchase
// use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase
}
+ func observerModeDidInitiateRestorePurchases(onStartRestore: @escaping () -> Void,
+ onFinishRestore: @escaping () -> Void) {
+ // use the onStartRestore and onFinishRestore callbacks to notify AdaptyUI about the process of the restore
+ }
```
---
# File: migration-to-ios-sdk-34
---
---
title: "Migrate Adapty iOS SDK to v3.4"
description: "Migrate to Adapty iOS SDK v3.4 for better performance and new monetization features."
---
Adapty SDK 3.4.0 là một bản phát hành lớn, giới thiệu các cải tiến yêu cầu bạn thực hiện các bước migration.
## Cập nhật khởi tạo Adapty SDK \{#update-adapty-sdk-activation\}
### Trong quá trình đăng nhập/đăng ký \{#during-loginsignup\}
Nếu bạn đang 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 sử dụng customer user ID này trước đây**, Adapty sẽ tự động liên kết nó với hồ sơ 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ơ đượ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 người.
:::
Hãy chờ `identify` hoàn thành (trong callback `onSuccess` của nó) trước khi gọi các phương thức SDK khác. Các lệnh gọi đồng thời có thể rơi vào hồ sơ ẩn danh. Xem [Thứ tự gọi trong Kotlin Multiplatform SDK](kmp-sdk-call-order).
```kotlin showLineNumbers
Adapty.identify("YOUR_USER_ID") // Unique for each user
.onSuccess {
// successful identify
}
.onError { error ->
// handle the error
}
```
### 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.
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ơ ẩn danh mới và chỉ chuyển sang hồ sơ hiện có sau khi bạn gọi `identify`.
Bạn có thể truyền customer user ID hiện có (cái bạn đã sử dụng trước đây) hoặc một cái mới. Nếu bạn truyền một cái mới, hồ sơ 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ơ ẩn danh không ảnh hưởng đến các dashboard phân tích, vì số lần 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 duy nhất của ứ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 lặp lại, hoặc liệu customer user ID hiện có có được sử dụng hay không.
Việc tạo hồ sơ (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 ứng dụng sẽ không tạo thêm sự kiện cài đặt.
Nếu bạn muốn đếm số lần 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).
:::
```kotlin showLineNumbers
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one.
.build()
```
### Đăng xuất người dùng \{#log-users-out\}
Nếu bạn có nút đăng xuất cho người dùng, hãy sử dụng phương thức `logout`.
:::important
Đăng xuất người dùng sẽ tạo một hồ sơ ẩn danh mới cho người dùng.
:::
```kotlin showLineNumbers
Adapty.logout()
.onSuccess {
// successful logout
}
.onError { error ->
// handle the error
}
```
:::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ẽ vẫn 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ơ ẩn danh của họ.
2. Khi người dùng đăng nhập vào tài khoản của họ, Adapty chuyển sang làm việc với hồ sơ đã 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 sẽ gán customer user ID cho hồ sơ hiện tại, vì vậy 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ơ), bạn cần lấy mức độ truy cập thực tế sau khi chuyển đổi hồ sơ. Bạn có thể gọi [`getProfile`](kmp-check-subscription-status) ngay sau khi xác định, hoặc [lắng nghe các cập nhật hồ sơ](kmp-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 trong ứng dụng! Chúc bạn thành công với việc kiếm tiền từ ứng dụng!
Để khai thác nhiều hơn từ Adapty, bạn có thể khám phá các chủ đề sau:
- [**Kiểm thử**](troubleshooting-test-purchases): Đảm bảo rằng mọi thứ hoạt động như mong đợi
- [**Tích hợp**](configuration): Tích hợp với các dịch vụ attribution marketing và phân tích chỉ bằng một dòng code
- [**Đặt thuộc tính hồ sơ tùy chỉnh**](kmp-setting-user-attributes): Thêm các 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 những người dùng khác nhau
---
# File: adapty-sdk-integration-skill-kmp
---
---
title: "Tích hợp Adapty vào ứng dụng Kotlin Multiplatform với kỹ năng tích hợp SDK"
description: "Sử dụng kỹ năng adapty-sdk-integration để tích hợp Adapty SDK vào ứng dụng Kotlin Multiplatform của bạn từ đầu đến cuối với công cụ AI coding."
---
:::important
Kỹ năng 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-kmp) — 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-kmp
---
---
title: "Tích hợp Adapty vào ứng dụng Kotlin Multiplatform 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 Kotlin Multiplatform của bạn bằ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 Kotlin Multiplatform với sự hỗ trợ của 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ỳ dòng code SDK nào. Bạn có thể thực hiện điều này thông qua một LLM skill tương tác, hoặc thủ công qua Dashboard.
### Phương pháp 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.
### Phương pháp dùng Dashboard \{#dashboard-approach\}
Nếu bạn muốn cấu hình mọi thứ thủ công, đâ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ị trên dashboard thay bạn — bạn sẽ cần tự 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 KMP của bạn nhắm đến cả hai nền tảng. Đây là yêu cầu bắt buộc để thanh toán 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 trực tiếp sản phẩm 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ó vào 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 theo 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 đã có đủ năm mục trên, bạn đã sẵn sàng viết code. Hãy cho LLM của bạn biết: "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 tùy thuộc vào từng 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 editor của bạn và cấu hình Context7 server. Để thiết lập thủ công, xem [kho GitHub của Context7](https://github.com/upstash/context7).
Sau khi cấu hình, hãy 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 Kotlin Multiplatform 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 rất 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ột để đảm bảo mọi thứ hoạt động đúng.
:::
### Dùng tài liệu dạng văn bản thuần \{#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 văn bản Markdown thuần. Thêm `.md` vào cuối URL, hoặc nhấp vào **Copy for LLM** bên dưới tiêu đề bài viết. Ví dụ: [adapty-cursor-kmp.md](https://adapty.io/docs/vi/adapty-cursor-kmp.md).
Mỗi giai đoạn trong [hướng dẫn triển khai](#implementation-walkthrough) bên dưới có một khối "Gửi cho LLM của bạn" với các link `.md` để dán vào.
Để lấy nhiều tài liệu cùng 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 sẽ đưa bạn qua quá trình tích hợp Adapty theo đúng thứ tự triển khai. Mỗi giai đoạn bao gồm tài liệu cần gửi cho LLM, kết quả mong đợi 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 của bạn phân tích dự án và tạo kế hoạch triển khai. Nếu công cụ AI của bạn có chế độ lập kế hoạch (như chế độ plan của Cursor hoặc Claude Code), hãy 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 bất kỳ code nào.
Hãy cho LLM biết bạn dùng phương pháp nào để xử lý thanh toán — điều này ảnh hưởng đến các hướng dẫn nó cần theo:
- [**Adapty Paywall Builder**](adapty-paywall-builder): Bạn tạo paywall trong công cụ no-code của Adapty, và SDK tự động hiển thị chúng.
- [**Paywall tự tạo thủ công**](kmp-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ý thanh toán.
- [**Observer mode**](observer-vs-full-mode): Bạn giữ nguyên hạ tầng thanh toán 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](kmp-quickstart-paywalls).
### Cài đặt và cấu hình SDK \{#install-and-configure-the-sdk\}
Thêm dependency Adapty SDK qua Gradle 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-kotlin-multiplatform)
Gửi nội dung này cho LLM của bạn:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/vi/sdk-installation-kotlin-multiplatform.md
```
:::tip[Checkpoint]
- **Kết quả mong đợi:** Ứng dụng build và chạy được. Logcat (Android) hoặc Xcode console (iOS) 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ực từ App settings chưa.
:::
### Hiển thị paywall và xử lý thanh toán \{#show-paywalls-and-handle-purchases\}
Lấy paywall theo placement ID, hiển thị nó và xử lý các sự kiện thanh toán. Các hướng dẫn bạn cần phụ thuộc vào cách bạn xử lý thanh toán.
Hãy kiểm tra từng giao dịch trong sandbox khi bạn tiến hành — đừng đợi đến cuối. Xem [Kiểm tra thanh toán trong sandbox](test-purchases-in-sandbox) để biết hướng dẫn thiết lập.
tùy chọn
mặc định: `en`
|Định danh của [bản dịch paywall](add-paywall-locale-in-adapty-paywall-builder). Tham số này được kỳ vọng là một mã ngôn ngữ gồm một hoặc hai thẻ con được phân tách bằng ký tự gạch ngang (**-**). Thẻ con đầu tiên là ngôn ngữ, thẻ con thứ hai là vùng.
Ví dụ: `en` nghĩa là tiếng Anh, `pt-br` đại diện cho tiếng Bồ Đào Nha ở Brazil.
Xem [Các bản dịch và mã ngôn ngữ](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: `AdaptyPaywallFetchPolicy.Default` |Theo mặc định, SDK sẽ cố tải dữ liệu từ máy chủ và trả về dữ liệu đã được lưu trong cache nếu thất bại. Chúng tôi khuyến nghị lựa 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 thường xuyên gặp vấn đề với kết nối internet không ổn định, hãy cân nhắc sử dụng `AdaptyPaywallFetchPolicy.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ọ có kém đến đâu. Cache được cập nhật thường xuyên, nên 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òn nguyên khi khởi động lại ứng dụng và chỉ bị xóa khi cài đặt lại ứng dụng hoặc xóa 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 để tải 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. 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 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ì thao tác có thể bao gồm nhiều yêu cầu khác nhau bên dưới.
Với Kotlin Multiplatform: Bạn có thể tạo `TimeInterval` bằng các hàm mở rộng (như `5.seconds`, trong đó `.seconds` từ `import com.adapty.utils.seconds`), hoặc `TimeInterval.seconds(5)`. Để không giới hạn, sử dụng `TimeInterval.INFINITE`.
| Tham số phản hồi: | Tham số | Mô tả | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Một đối tượng [`AdaptyPaywall`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall/) với 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 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 nút **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ó bao gồm `ViewConfiguration` hay không — điều này cho biết paywall được tạo bằng Paywall Builder. Thông tin 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ư một paywall Paywall Builder; nếu không, [xử lý nó như một remote config paywall](present-remote-config-paywalls-kmp). Sử dụng phương thức `createPaywallView` để tải cấu hình view. ```kotlin showLineNumbers if (paywall.hasViewConfiguration) { AdaptyUI.createPaywallView( paywall = paywall, loadTimeout = 5.seconds, preloadProducts = true ).onSuccess { paywallView -> // use paywallView }.onError { error -> // handle the error } } else { // use your custom logic } ``` | Tham số | Bắt buộc | Mô tả | | :--------------------------- | :------------- | :----------------------------------------------------------- | | **paywall** | bắt buộc | Đối tượng `AdaptyPaywall` để lấy controller cho paywall mong muốn. | | **loadTimeout** | tùy chọn | 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 nhiều yêu cầu khác nhau bên dưới. Bạn có thể sử dụng các hàm mở rộng như `5.seconds` từ `kotlin.time.Duration.Companion`. | | **preloadProducts** | tùy chọn | Đặt thành `true` để tải trước sản phẩm nhằm cải thiện hiệu suất. Khi được bật, sản phẩm được tải trước, giảm thời gian cần thiết để hiển thị paywall. | | **productPurchaseParams** | tùy chọn | Một map từ [`AdaptyProductIdentifier`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-product-identifier/) sang [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). Sử dụng tham số này để cấu hình các tham số mua hàng cụ thể như ưu đãi cá nhân hóa hoặc tham số cập nhật gói đăng ký cho từng sản phẩm trong paywall. | :::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 dịch cho Paywall Builder](add-paywall-locale-in-adapty-paywall-builder). ::: Sau khi tải xong, [trình bày paywall](kmp-present-paywalls). ## Lấy paywall cho đối tượng mặc định để tải nhanh hơn \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} 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 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 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 mô tả chi tiết trong phần [Lấy thông tin Paywall](#fetch-paywall-designed-with-paywall-builder) ở trên. :::warning Lý do chúng tôi khuyến nghị sử dụng `getPaywall` Phương thức `getPaywallForDefaultAudience` có một số hạn chế đáng kể: - **Các vấn đề tiềm ẩn về tương thích ngược**: Nếu bạn cần hiển thị các paywall khác nhau cho các phiên bản ứng dụng khác nhau (hiện tại và tương lai), bạn có thể gặp khó khăn. Bạn sẽ phải thiết kế paywall hỗ trợ phiên bản hiện tại (cũ) hoặc chấp nhận rằng người dùng với phiên bản hiện tại (cũ) có thể gặp sự cố với paywall không được render. - **Mất khả năng nhắm mục tiêu**: Tất cả người dùng sẽ thấy cùng một paywall được thiết kế cho đối tượng **All Users**, nghĩa là bạn mất đi khả năng nhắm mục tiêu cá nhân hóa (bao gồm theo quốc gia, attribution marketing hoặc các thuộc tính tùy chỉnh của riêng bạn). Nếu bạn sẵn sàng chấp nhận những hạn chế này để hưởng lợi từ việc lấy paywall nhanh hơn, hãy sử dụng phương thức `getPaywallForDefaultAudience` như sau. Ngược lại, hãy sử dụng `getPaywall` được mô tả [ở trên](#fetch-paywall-designed-with-paywall-builder). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` | 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 dịch paywall](add-remote-config-locale). Tham số này được kỳ vọng là một mã ngôn ngữ gồm một hoặc nhiều thẻ con được phân tách bằng ký tự gạch ngang (**-**). Thẻ con đầu tiên là ngôn ngữ, thẻ con thứ hai là vùng.
Ví dụ: `en` nghĩa là tiếng Anh, `pt-br` đại diện cho tiếng Bồ Đào Nha ở Brazil.
Xem [Các bản dịch và mã ngôn ngữ](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: `AdaptyPaywallFetchPolicy.Default` |Theo mặc định, SDK sẽ cố tải dữ liệu từ máy chủ và trả về dữ liệu đã được lưu trong cache nếu thất bại. Chúng tôi khuyến nghị lựa 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 thường xuyên gặp vấn đề với kết nối internet không ổn định, hãy cân nhắc sử dụng `AdaptyPaywallFetchPolicy.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ọ có kém đến đâu. Cache được cập nhật thường xuyên, nên 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òn nguyên khi khởi động lại ứng dụng và chỉ bị xóa khi cài đặt lại ứng dụng hoặc xóa thủ công.
| ## Tùy chỉnh tài nguyên \{#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 sẵn: `hero_image` và `hero_video`. Trong một bundle custom asset, bạn nhắm mục tiêu các phần tử này theo 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 ID tùy chỉnh](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ị hình ảnh xem trước cục bộ trong khi hình ảnh chính từ xa đang tải. - Hiển thị hình ả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 SDK lên phiên bản 3.7.0 trở lên. ::: Đây là ví dụ về cách bạn có thể cung cấp custom assets thông qua một map: :::info Kotlin Multiplatform SDK chỉ hỗ trợ tài nguyên cục bộ. Đối với nội dung từ xa, bạn nên tải xuống và lưu vào cache cục bộ trước khi sử dụng chúng trong custom assets. ::: ```kotlin showLineNumbers // Import generated Res class for accessing resources viewModelScope.launch { // Get URIs for bundled resources using Res.getUri() val heroImagePath = Res.getUri("files/images/hero_image.png") val demoVideoPath = Res.getUri("files/videos/demo_video.mp4") // Or read image as byte data val imageByteData = Res.readBytes("files/images/avatar.png") // Create custom assets map val customAssets: Map
## Số lượt xem paywall quá lớn \{#the-paywall-view-number-is-too-big\}
**Sự cố**: Số lượt xem paywall hiển thị gấp đôi so với dự kiến.
**Nguyên nhân**: Bạn có thể đ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 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.
---
# File: kmp-implement-paywalls-manually
---
---
title: "Implement paywalls manually in Kotlin Multiplatform SDK"
description: "Learn how to implement paywalls manually in your Kotlin Multiplatform app with Adapty SDK."
---
## Accept purchases
If you are working with paywalls you've implemented yourself, you can delegate handling purchases to Adapty, using the `makePurchase` method. This way, we will handle all the user scenarios, and you will only need to handle the purchase results.
:::important
`makePurchase` works with products created in the Adapty dashboard. Make sure you configure products and ways to retrieve them in the dashboard by following the [quickstart guide](quickstart).
:::
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 được kỳ vọng là mã ngôn ngữ gồm một hoặc nhiều thẻ phụ được phân tách bằng ký tự dấu trừ (**-**). Thẻ phụ đầu tiên là ngôn ngữ, thẻ 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: `AdaptyPaywallFetchPolicy.Default` |Theo mặc định, SDK sẽ cố tải dữ liệu từ server 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 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 thường xuyên gặp vấn đề kết nối internet không ổn định, hãy cân nhắc dùng `AdaptyPaywallFetchPolicy.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 thời gian tải sẽ nhanh hơn, bất kể chất lượng kết nối internet của họ. Cache được cập nhật thường xuyên nên an toàn khi sử dụng trong phiên làm việc để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn nguyên sau 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 ở hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và [paywall dự phòng](kmp-use-fallback-paywalls). Chúng tôi cũng sử dụng CDN để tải paywall nhanh hơn và một server dự phòng độc lập trong trường hợp CDN không 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 paywall mới nhất 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 hạn chậm hơn một chút so với thời gian 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.
| Đừng hardcode ID sản phẩm! Vì paywall được cấu hình từ xa, các sản phẩm khả dụng, số lượng sản phẩm và ư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 mà không cần thay đổi code. Thứ duy nhất bạn phải hardcode là placement ID. Các tham số phản hồi: | Tham số | Mô tả | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Một đối tượng [`AdaptyPaywall`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall/) với: 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ó: ```kotlin showLineNumbers Adapty.getPaywallProducts(paywall).onSuccess { products -> // the requested products }.onError { error -> // handle the error } ``` Các tham số phản hồi: | Tham số | Mô tả | | :-------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Danh sách các đối tượng [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) với: định danh sản phẩm, tên sản phẩm, giá, tiền tệ, thời hạn gói đă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 sẽ cần truy cập các thuộc tính này từ đối tượng [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/). 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 đề sản phẩm, sử dụng `product.localizedTitle`. Lưu ý rằng việc bản địa hóa dựa trên quốc gia cửa hàng được chọn của người dùng chứ 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`. Việc 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.subscriptionDetails?.localizedSubscriptionPeriod`. Việc bản địa hóa này dựa trên ngôn ngữ của thiết bị. Để lấy chu kỳ gói đăng ký theo chương trình, sử dụng `product.subscriptionDetails?.subscriptionPeriod`. Từ đó bạn có thể truy cập enum `unit` để lấy độ dài (tức là DAY, WEEK, MONTH, YEAR hoặc UNKNOWN). Giá trị `numberOfUnits` sẽ cho bạn biết số đơn vị chu kỳ. Ví dụ: đối với gói đăng ký theo quý, bạn sẽ thấy `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ỉ số khác cho biết gói đăng ký có ưu đãi giới thiệu, hãy xem thuộc tính `product.subscriptionDetails?.introductoryOfferPhases`. Đâ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: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 được kỳ vọng là mã ngôn ngữ gồm một hoặc nhiều thẻ phụ được phân tách bằng ký tự dấu trừ (**-**). Thẻ phụ đầu tiên là ngôn ngữ, thẻ 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: `AdaptyPaywallFetchPolicy.Default` |Theo mặc định, SDK sẽ cố tải dữ liệu từ server 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 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 thường xuyên gặp vấn đề kết nối internet không ổn định, hãy cân nhắc dùng `AdaptyPaywallFetchPolicy.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 thời gian tải sẽ nhanh hơn, bất kể chất lượng kết nối internet của họ. Cache được cập nhật thường xuyên nên an toàn khi sử dụng trong phiên làm việc để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn nguyên sau 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.
| --- # File: present-remote-config-paywalls-kmp --- --- title: "Hiển thị paywall được thiết kế bằng remote config trong Kotlin Multiplatform SDK" description: "Khám phá cách trình bày paywall remote config trong Adapty Kotlin Multiplatform 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 để trình bày nó cho người dùng. Vì Remote Config linh hoạt và phụ thuộc vào nhu cầu của bạn, bạn hoàn toàn kiểm soát những gì được đưa vào và giao diện paywall sẽ trông như thế nào. Chúng tôi cung cấp một phương thức để lấy cấu hình remote, giúp bạn tự do hiển thị 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, hãy truy cập thuộc tính `remoteConfig` và trích xuất các giá trị cần thiết. ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String // use the remote config values }.onError { error -> // handle the error } ``` Tại đây, sau khi đã nhận được tất cả các giá trị cần thiết, đã đến lúc render và lắp ráp chúng thành một trang có giao diện hấp dẫn. 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 liền mạch và thân thiện với người dùng trên nhiều thiết bị. :::warning Hãy đảm bảo [ghi lại sự kiện xem paywall](present-remote-config-paywalls-kmp#track-paywall-view-events) như mô tả bên dưới, để Adapty analytics có thể thu thập thông tin cho các 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 luồng 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](kmp-making-purchases). Chúng tôi khuyên bạn nên [tạo một paywall dự phòng gọi là fallback paywall](kmp-use-fallback-paywalls). Paywall dự phòng 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 các tình huống này. ## Ghi lại sự kiện xem paywall \{#track-paywall-view-events\} Adapty giúp bạn đo lường hiệu suất của các paywall. Trong khi chúng tôi tự động thu thập dữ liệu về các lần 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 một paywall. Để ghi lại sự kiện xem paywall, chỉ cần gọi `.logShowPaywall(paywall)`, và nó sẽ được phản ánh trong các chỉ số 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ị các paywall được tạo trong [paywall builder](adapty-paywall-builder). ::: ```kotlin showLineNumbers Adapty.logShowPaywall(paywall = paywall) .onSuccess { // paywall view logged successfully } .onError { error -> // handle the error } ``` Tham số yêu cầu: | Tham số | Bắt buộc | Mô tả | | :---------- | :------- |:-------------------------------------------------------------------------------------------------------| | **paywall** | bắt buộc | Một đối tượng [`AdaptyPaywall`](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-paywall/). | --- # File: kmp-making-purchases --- --- title: "Thực hiện mua hàng trong ứng dụng với Kotlin Multiplatform 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 thôi là đủ để hỗ trợ mua hàng nếu bạn dùng [Paywall Builder](adapty-paywall-builder) để tùy chỉnh paywall. Nếu 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. Đây là cầu nối giúp người dùng tương tác với paywall và tiến hành giao dịch mong muốn. Nếu paywall của bạn có ưu đãi đang hoạt động cho sản phẩm người dùng 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 tính đủ điều kiện nhận ưu đãi giới thiệu của người dùng 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 bị từ chối khi phát hành, đồng thời có thể 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 thành cấu hình ban đầu](quickstart) mà không bỏ qua bước nào. Nếu thiếu, chúng tôi không thể xác thực giao dịch mua. ## Thực hiện mua hàng \{#make-purchase\} :::note **Đang dùng [Paywall Builder](adapty-paywall-builder)?** Giao dịch được xử lý tự động — bạn có thể bỏ qua bước này. **Muốn có hướng dẫn từng bước?** Xem [hướng dẫn quickstart](kmp-implement-paywalls-manually) để có hướng dẫn triển khai đầy đủ từ đầu đến cuối. ::: ```kotlin showLineNumbers Adapty.makePurchase(product = product).onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // Grant access to the paid features } } is AdaptyPurchaseResult.UserCanceled -> { // Handle the case where the user canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Handle deferred purchases (e.g., the user will pay offline with cash) } } }.onError { error -> // Handle the error } ``` Tham số yêu cầu: | Tham số | Bắt buộc | Mô tả | | :---------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------| | **Product** | bắt buộc | Đối tượng [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) 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://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/) cung cấp thông tin toàn diện 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 liệu 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 [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 gói đăng ký mới thay vì gia hạn gói hiện tại, cách thức hoạt động phụ thuộc vào cửa hàng. Với Google Play, gói đăng ký không tự động được cập nhật. Bạn cần quản lý việc chuyển đổi trong code ứng dụng như mô tả dưới đây. Để thay thế gói đăng ký bằng gói khác trên Android, hãy gọi phương thức `.makePurchase()` với tham số bổ sung: ```kotlin showLineNumbers val subscriptionUpdateParams = AdaptyAndroidSubscriptionUpdateParameters( oldSubVendorProductId = "old_subscription_product_id", replacementMode = AdaptyAndroidSubscriptionUpdateReplacementMode.CHARGE_FULL_PRICE ) val purchaseParams = AdaptyPurchaseParameters.Builder() .setSubscriptionUpdateParams(subscriptionUpdateParams) .build() Adapty.makePurchase( product = product, parameters = purchaseParams ).onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // successful cross-grade } is AdaptyPurchaseResult.UserCanceled -> { // user canceled the purchase flow } is AdaptyPurchaseResult.Pending -> { // the purchase has not been finished yet, e.g. user will pay offline by cash } } }.onError { error -> // Handle the error } ``` Tham số yêu cầu bổ sung: | Tham số | Bắt buộc | Mô tả | |:---------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **parameters** | tùy chọn | Đối tượng [`AdaptyAndroidSubscriptionUpdateParameters`](https://kmp.adapty.io/////adapty/com.adapty.kmp.models/-adapty-android-subscription-update-parameters/) được truyền qua [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). | 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: - [Giới thiệu 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ỉ dùng được khi nâng cấp gói đăng ký. Hạ cấp không được hỗ trợ. - 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ỉ diễn ra khi kỳ thanh toán gói đăng ký 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';Một đối tượng [`AdaptyProfile`](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-profile/). Model này chứa thông tin về mức độ truy cập, các gói đăng ký và 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-kmp --- --- title: "Triển khai Observer mode trong Kotlin Multiplatform 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 Kotlin Multiplatform SDK." --- Nếu bạn đã có cơ sở 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ể tham khảo [Observer mode](observer-vs-full-mode). Ở dạng cơ bản, Observer Mode cung cấp phân tích 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 phù hợp với nhu cầu của bạn, bạn chỉ cần: 1. Bật Observer mode 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 [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform). 2. [Báo cáo giao dịch](report-transactions-observer-mode-kmp) từ cơ sở hạ tầng mua hàng hiện có của bạn tới Adapty. ## Thiết lập Observer mode \{#observer-mode-setup\} Bật Observer mode nếu bạn tự xử lý giao dịch mua 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à phân tích. :::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ý việc đó. ::: ```kotlin showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withObserverMode(true) // default false .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised in observer mode") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } ``` Các 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 các 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ố bước cấu hình trong Observer mode. Đây là những việc bạn cần làm ngoài các bước trên: 1. Hiển thị paywall như bình thường đối với [remote config paywalls](present-remote-config-paywalls-kmp). 3. [Liên kết paywall](report-transactions-observer-mode-kmp) với các giao dịch mua. --- # File: report-transactions-observer-mode-kmp --- --- title: "Báo cáo giao dịch trong Observer Mode trong Kotlin Multiplatform 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 Kotlin Multiplatform SDK." --- Trong Observer Mode, Adapty SDK không thể tự động 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. Điều quan trọng là phải thiết lập điều này **trước khi** phát hành ứng dụng để tránh lỗi trong analytics. Sử dụng `reportTransaction` để báo cáo từng giao dịch một cách tường minh để Adapty nhận biết được. :::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 ra giao dịch đó, nó 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 bao gồm `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. ```kotlin showLineNumbers Adapty.reportTransaction( transactionId = "your_transaction_id", variationId = paywall.variationId ).onSuccess { profile -> // Transaction reported successfully // profile contains updated user data }.onError { error -> // handle the error } ``` Các tham số: | Tham số | Bắt buộc | Mô tả | | --------------- | -------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | bắt buộc | ID giao dịch từ giao dịch mua hàng trên cửa hàng ứng dụng của bạn. Đây thường là purchase token hoặc transaction identifier được cửa hàng trả về. | | variationId | tùy chọn | Chuỗi định danh 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://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-paywall/). | --- # File: kmp-troubleshoot-purchases --- --- title: "Khắc phục sự cố mua hàng trong Kotlin Multiplatform SDK" description: "Khắc phục sự cố mua hàng trong Kotlin Multiplatform 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 Kotlin Multiplatform 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 thành 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**: Điều này thường cho thấy quá trình thiết lập Google Play Store chưa hoàn chỉnh hoặc có vấn đề về cấu hình. **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 được gọi hai lần \{#makepurchase-is-invoked-twice\} **Vấn đề**: Phương thức `makePurchase` đang được gọi nhiều lần cho cùng một giao dịch mua. **Nguyên nhân**: Điều này thường xảy ra khi flow mua hàng bị kích hoạt nhiều lần do các 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 \{#adaptyelrorcantmakepayments-in-observer-mode\} **Vấn đề**: Bạn đang nhận được lỗi `AdaptyError.cantMakePayments` khi sử dụng `makePurchase` trong chế độ observer. **Nguyên nhân**: Trong chế độ observer, bạn cần tự xử lý giao dịch mua hàng phía mình, không sử dụng phương thức `makePurchase` của Adapty. **Giải pháp**: Nếu bạn dùng `makePurchase` để thực hiện giao dịch, hãy tắt chế độ observer. Bạn cần chọn một trong hai: dùng `makePurchase` hoặc tự xử lý giao dịch mua hàng trong chế độ observer. Xem [Triển khai chế độ Observer](implement-observer-mode-kmp) để 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 đang 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 của 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 đang gặp vấn đề với `makePurchasesCompletionHandlers` không tìm thấy. **Nguyên nhân**: Vấn đề này thường liên quan đến các sự cố khi kiểm thử trong môi trường 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. --- # File: kmp-user --- --- title: "Users & access in Kotlin Multiplatform SDK" description: "Learn how to work with users and access levels in your Kotlin Multiplatform app with Adapty SDK." --- This page contains all guides for working with users and access levels in your Kotlin Multiplatform app. Choose the topic you need: - **[Identify users](kmp-identifying-users)** - Learn how to identify users in your app - **[Update user data](kmp-setting-user-attributes)** - Set user attributes and profile data - **[Listen for subscription status changes](kmp-listen-subscription-changes)** - Monitor subscription changes in real-time - **[Kids Mode](kids-mode-kmp)** - Implement Kids Mode for your app --- # File: kmp-identifying-users --- --- title: "Xác định người dùng trong Kotlin Multiplatform SDK" description: "Xác định người dùng trong Adapty để cải thiện trải nghiệm gói đăng ký được cá nhân hóa." --- 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 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ó vào tham số `customerUserId` của phương thức `.activate()`: ```kotlin showLineNumbers Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("YOUR_USER_ID") .build() ).onSuccess { // successful activation }.onError { 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. ::: ### Đặt customer user ID sau khi cấu hình \{#setting-customer-user-id-after-configuration\} Nếu bạn chưa có user ID khi 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()`. 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ừ trạng thái ẩn danh sang đã xác thực. ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID").onSuccess { // successful identify }.onError { error -> // handle the error } ``` Các 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ủ Adapty đã có thông tin về người dùng đó. Trong những tình huống 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, hãy lưu ý rằng 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()`: ```kotlin showLineNumbers Adapty.logout().onSuccess { // successful logout }.onError { error -> // handle the error } ``` Sau đó, bạn có thể đăng nhập người dùng bằng phương thức `.identify()`. ## Gán `appAccountToken` (iOS) \{#assign-appaccounttoken-ios\} [`iosAppAccountToken`](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 bạn. 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 bạn. Hãy sử dụng 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 nhiều thiết bị. Đ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 `iosAppAccountToken` cùng với `customerUserId`. Nếu chỉ truyền token, nó sẽ không được đưa vào giao dịch. ::: ```kotlin showLineNumbers // During configuration: Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId( id = "YOUR_USER_ID", iosAppAccountToken = "YOUR_IOS_APP_ACCOUNT_TOKEN" ) .build() ).onSuccess { // successful activation }.onError { error -> // handle the error } // Or when identifying users Adapty.identify( customerUserId = "YOUR_USER_ID", iosAppAccountToken = "YOUR_IOS_APP_ACCOUNT_TOKEN" ).onSuccess { // successful identify }.onError { error -> // handle the error } ``` ## Đặ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 người dùng. Các ID này giúp Google Play xác định giao dịch mua trong khi vẫn giữ thông tin người dùng ở dạng ẩn danh, điều này đặc biệt quan trọng cho việc 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 ID ẩn danh cho phép Google Play theo dõi giao dịch mua mà không tiết lộ định danh người dùng thực. :::important Bạn phải luôn truyền `androidObfuscatedAccountId` cùng với `customerUserId`. Nếu chỉ truyền obfuscated account ID, nó sẽ không được đưa vào giao dịch. ::: ```kotlin showLineNumbers // During configuration: Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId( id = "YOUR_USER_ID", androidObfuscatedAccountId = "YOUR_OBFUSCATED_ACCOUNT_ID" ) .build() ).onSuccess { // successful activation }.onError { error -> // handle the error } // Or when identifying users Adapty.identify( customerUserId = "YOUR_USER_ID", androidObfuscatedAccountId = "YOUR_OBFUSCATED_ACCOUNT_ID" ).onSuccess { // successful identify }.onError { error -> // handle the error } ``` ## 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: kmp-setting-user-attributes --- --- title: "Đặt thuộc tính người dùng trong Kotlin Multiplatform SDK" description: "Tìm hiểu cách đặt thuộc tính người dùng trong Adapty để phân khúc đối tượng tốt hơn." --- 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 của mình. 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, gọi phương thức `.updateProfile()`: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.FEMALE) .withBirthday(AdaptyProfile.Date(1970, 1, 3)) Adapty.updateProfile(builder.build()) .onSuccess { // profile updated successfully } .onError { error -> // handle the error } ``` 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 `phoneNumber
firstName
lastName
| String | | gender | Enum, các giá trị được phép: `AdaptyProfile.Gender.FEMALE`, `AdaptyProfile.Gender.MALE`, `AdaptyProfile.Gender.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 ngoại ngữ, đó có thể là trình độ kiến thức 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. ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() builder.withCustomAttribute("key1", "value1") ``` Để xóa key hiện có, dùng phương thức `.withRemovedCustomAttribute()`: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() builder.withRemovedCustomAttribute("key2") ``` Đôi khi bạn cần biết những thuộc tính tùy chỉnh nào đã được cài đặt 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 cập nhật mới nhất, vì thuộc tính người dùng có thể được gửi từ nhiều thiết bị khác nhau bất kỳ lúc nào, nên các thuộc tính trên server có thể đã thay đổi sau lần đồng bộ cuối. ::: ### 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 dài 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 bất kỳ ký tự nào sau đây: `_` `-` `.` - Giá trị có thể là chuỗi hoặc số thực (float) với tối đa 50 ký tự. --- # File: kmp-listen-subscription-changes --- --- title: "Kiểm tra trạng thái gói đăng ký trong Kotlin Multiplatform 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 khả năng giữ chân khách hàng trong ứng dụng Kotlin Multiplatform 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 chèn thủ công các 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ý, 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à các thuộc tính của đối tượng [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/). 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](android-identifying-users#setting-customer-user-id-on-configuration), rồi cập nhật nó mỗi khi có thay đổi. Bằng cách này, bạn có thể sử dụng đối tượng profile mà không cần phải yêu cầu lại nhiều lần. Để 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 profile như mô tả trong phần [Lắng nghe các cập nhật profile, bao gồm mức độ truy cập](android-listen-subscription-changes) 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()`: ```kotlin showLineNumbers Adapty.getProfile().onSuccess { profile -> // check the access }.onError { error -> // handle the error } ``` Tham số phản hồi: | Tham số | Mô tả | | --------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Profile |Đối tượng [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/). Thông thường, bạn chỉ cần kiểm tra trạng thái mức độ truy cập của profile để xác định xem người dùng có quyền truy cập premium vào ứng dụng hay không.
Phương thức `.getProfile` luôn cố gắng truy vấn API nên trả về kết quả mới nhất. Nếu vì lý do nào đó (ví dụ: không có kết nối internet), Adapty SDK không lấy được thông tin từ máy chủ, dữ liệu từ bộ nhớ cache sẽ được trả về. Cần lưu ý rằng Adapty SDK cập nhật cache `AdaptyProfile` thường xuyên để đảm bảo thông tin luôn được cập nhật nhất có thể.
| 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. Một ứng dụng có thể có nhiều mức độ truy cập. 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". Nhưng 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ể dùng mức độ truy cập mặc định "premium". Đây là ví dụ kiểm tra mức độ truy cập "premium" mặc định: ```kotlin showLineNumbers Adapty.getProfile().onSuccess { profile -> if (profile.accessLevels["premium"]?.isActive == true) { // grant access to premium features } }.onError { error -> // handle the error } ``` ### 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: ```kotlin showLineNumbers Adapty.setOnProfileUpdatedListener { 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. ### Bộ nhớ cache trạng thái gói đăng ký \{#subscription-status-cache\} Bộ nhớ 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ể yêu cầu 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ơ người dùng. 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 cache luôn nhất quán với máy chủ. --- # File: kmp-deal-with-att --- --- title: "Xử lý ATT trong Kotlin Multiplatform SDK" description: "Bắt đầu với Adapty trên Kotlin Multiplatform để đơ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 xác thực theo dõi ứng dụng cho 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/) đến Adapty. ```kotlin showLineNumbers val profileParameters = AdaptyProfileParameters.Builder() .withAttStatus(3) // 3 = ATTrackingManagerAuthorizationStatusAuthorized .build() Adapty.updateProfile(profileParameters) .onSuccess { // ATT status updated successfully } .onError { error -> // handle AdaptyError } ``` :::warning 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 đến các integration mà bạn đã cấu hình. ::: --- # File: kids-mode-kmp --- --- title: "Kids Mode trong Kotlin Multiplatform SDK" description: "Dễ dàng bật Kids Mode để tuân thủ chính sách Google. Không thu thập GAID hay dữ liệu quảng cáo trong Kotlin Multiplatform SDK." --- Nếu ứng dụng Kotlin Multiplatform của bạn dành cho trẻ em, bạn phải tuân theo các chính sách của [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 để đáp ứng các chính sách này và vượt qua quá trình kiểm duyệt của cửa hàng. ## Yêu cầu là gì? \{#whats-required\} Bạn cần cấu hình Adapty SDK để tắt việc 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ị sử dụng customer user ID một cách cẩn thận. User ID theo định dạng `tùy chọn
mặc định: `en`
| Mã định danh của bản địa hóa onboarding. Tham số này được kỳ vọng là một mã ngôn ngữ gồm một hoặc hai phần phụ được phân tách bằng dấu trừ (**-**). Phần phụ đầu tiên là ngôn ngữ, phần 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 thường xuyên gặp tình trạng mạng 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ó trải nghiệm tải nhanh hơn, bất kể kết nối internet của họ có bị gián đoạn thế nào. Cache được cập nhật thường xuyên, vì vậy việc sử dụng 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 được dọn dẹp thủ công.
Adapty SDK lưu trữ onboarding cục bộ theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và các onboarding dự phòng. Chúng tôi cũng sử dụng CDN để tải onboarding 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. 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 onboarding 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 này 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://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-onboarding/) chứa: mã định danh và cấu hình onboarding, Remote Config, và một số thuộc tính khác. | ## 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 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 vấn đề 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à phương pháp được khuyến nghị là lấy onboarding bằng phương thức `getOnboarding`, như đã 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, đò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ỏ khả năng nhắm mục tiêu theo quốc gia, attribution, hoặc các thuộc tính tùy chỉnh. Nếu tố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 dùng `getOnboardingForDefaultAudience` như bên dưới. Nếu không, hãy dùng `getOnboarding` như mô tả [ở trên](#fetch-onboarding). ::: ```kotlin showLineNumbers Adapty.getOnboardingForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` 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 onboarding. Tham số này được kỳ vọng là một mã ngôn ngữ gồm một hoặc hai phần phụ được phân tách bằng dấu trừ (**-**). Phần phụ đầu tiên là ngôn ngữ, phần thứ hai là vùng.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 thường xuyên gặp tình trạng mạng 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ó trải nghiệm tải nhanh hơn, bất kể kết nối internet của họ có bị gián đoạn thế nào. Cache được cập nhật thường xuyên, vì vậy việc sử dụng 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 được dọn dẹp thủ công.
Adapty SDK lưu trữ onboarding cục bộ theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và các onboarding dự phòng. Chúng tôi cũng sử dụng CDN để tải onboarding 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. 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 onboarding trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet kém.
| --- # File: kmp-present-onboardings --- --- title: "Trình bày onboarding trong Kotlin Multiplatform SDK" description: "Tìm hiểu cách trình bày 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 về việc render nó trong code ứng dụng Kotlin Multiplatform để hiển thị cho người dùng. Onboarding đó đã bao gồm cả nội dung hiển thị lẫn 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 Kotlin Multiplatform SDK](sdk-installation-kotlin-multiplatform) phiên bản 3.16.1 trở lên. 2. Bạn đã [tạo onboarding](create-onboarding). 3. Bạn đã thêm onboarding vào một [placement](placements). Adapty Kotlin Multiplatform SDK cung cấp hai cách để trình bày onboarding: - **Với Compose Multiplatform** - **Không dùng Compose Multiplatform** ## Với Compose Multiplatform \{#with-compose-multiplatform\} Để 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 dùng một lần. Nếu bạn cần hiển thị lại onboarding, hãy gọi `createOnboardingView` thêm một lần nữa để 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. ::: ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { AdaptyUI.createOnboardingView(onboarding = onboarding).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ### Cấu hình kiểu trình bày trên iOS \{#configure-ios-presentation-style\} Cấu hình cách onboarding được trình bày 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 giá trị `AdaptyUIIOSPresentationStyle.FULLSCREEN` (mặc định) hoặc `AdaptyUIIOSPresentationStyle.PAGESHEET`. ```kotlin showLineNumbers viewModelScope.launch { val view = AdaptyUI.createOnboardingView(onboarding = onboarding).getOrNull() view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET) } ``` ### Tùy chỉnh cách mở liên kết trong onboarding \{#customize-how-links-open-in-onboardings\} Theo mặc định, các liên kết trong onboarding sẽ mở trong trình duyệt trong ứng dụng. Điều này mang lại trải nghiệm liền mạch cho người dùng bằng cách hiển thị trang web ngay trong ứng dụng, không cần chuyển sang ứng dụng khác. Nếu bạn muốn mở liên kết bằng trình duyệt ngoài, có thể tùy chỉnh hành vi này bằng cách đặt tham số `externalUrlsPresentation` thành `AdaptyWebPresentation.EXTERNAL_BROWSER`: ```kotlin showLineNumbers viewModelScope.launch { AdaptyUI.createOnboardingView( onboarding = onboarding, externalUrlsPresentation = AdaptyWebPresentation.EXTERNAL_BROWSER // default – IN_APP_BROWSER ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ## Không dùng Compose Multiplatform \{#without-compose-multiplatform\} :::note `createNativeOnboardingView` là một phần của module core `io.adapty:adapty-kmp`. Nếu dự án của bạn không sử dụng Compose Multiplatform, bạn không cần dependency `io.adapty:adapty-kmp-ui`. ::: Để nhúng onboarding mà không dùng Compose Multiplatform, gọi `createNativeOnboardingView`. Phương thức này trả về một `AdaptyNativeOnboardingView` mà bạn thêm vào layout của mì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 delegate `onCustomAction` sẽ được kích hoạt với action ID từ builder. Bạn có thể tạo ID tùy ý, chẳng hạn như "allowNotifications".
```kotlin
class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver {
override fun onboardingViewOnCustomAction(
view: AdaptyUIOnboardingView,
meta: AdaptyUIOnboardingMeta,
actionId: String
) {
when (actionId) {
"openPaywall" -> {
// Display paywall from onboarding
// You would typically fetch and present a new paywall here
mainUiScope.launch {
// Example: Get paywall by placement ID
// val paywallResult = Adapty.getPaywall("your_placement_id")
// paywallResult.onSuccess { paywall ->
// val paywallViewResult = AdaptyUI.createPaywallView(paywall)
// paywallViewResult.onSuccess { paywallView ->
// paywallView.present()
// }
// }
}
}
"allowNotifications" -> {
// Handle notification permissions
}
else -> {
// Handle other custom actions
}
}
}
}
// Set up the observer
AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver())
```
2. Nhấp vào tên nhóm gói đăng ký. Bạn sẽ thấy danh sách sản phẩm trong mục **Subscriptions**.
3. Đảm bảo sản phẩm bạn đang kiểm tra được đánh dấu là **Ready to Submit**. Nếu chưa, hãy làm theo hướng dẫn trên trang [Product in App Store](app-store-products).
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 sản phẩm](create-product) với ID đó trong Adapty Dashboard.
## Bước 3. Kiểm tra tình trạng khả dụng của sản phẩm \{#step-4-check-product-availability\}
1. Quay lại **App Store Connect** và mở mục **Subscriptions** tương tự.
2. Nhấp vào tên nhóm gói đăng ký để xem danh sách sản phẩm.
3. Chọn sản phẩm bạn đang kiểm tra.
4. Cuộn đến mục **Availability** và kiểm tra rằng tất cả các quốc gia và khu vực cần thiết đều được liệt kê.
## Bước 4. Kiểm tra giá sản phẩm \{#step-5-check-product-prices\}
1. Một lần nữa, truy cập mục **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 **Subscription Pricing** và mở rộng mục **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 thanh toán của ứng dụng, tài khoản ngân hàng và biểu mẫu thuế \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\}
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 rằng **Paid Apps Agreement**, **Bank Account** và **Tax forms** của bạn đều hiển thị trạng thái **Active**.
Bằng cách làm theo các bước trên, bạn sẽ có thể khắc phục 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 \{#step-6-recreate-the-product-if-its-stuck\}
Các bước 1–5 có thể đều vượt qua — trạng thái `Approved`, Bundle ID khớp, API key hợp lệ — nhưng SDK vẫn trả về lỗi `1000 noProductIDsFound`. Trong trường hợp đó, sản phẩm có thể đang 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 xuất hiện trong đường dẫn 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 đợi tối đa 24 giờ để dữ liệu được đồng bộ.
---
# File: cantMakePayments-kmp
---
---
title: "Cách sửa lỗi Code-1003 cantMakePayment trong Kotlin Multiplatform SDK"
description: "Giải quyết lỗi không thể thực hiện 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: kmp-sdk-migration-guides
---
---
title: "Kotlin Multiplatform SDK Migration Guides"
description: "Migration guides for Adapty Kotlin Multiplatform SDK versions."
---
This page contains all migration guides for Adapty Kotlin Multiplatform SDK. Choose the version you want to migrate to for detailed instructions:
- **[Migrate to v3.15](migration-to-kmp-315)**
---
# File: migration-to-kmp-315
---
---
title: "Hướng dẫn migration lên Adapty Kotlin Multiplatform SDK 3.15.0"
description: "Các bước migration cho Adapty Kotlin Multiplatform SDK 3.15.0"
---
Adapty Kotlin Multiplatform SDK 3.15.0 là một bản phát hành lớn mang đến các tính năng và cải tiến mới, tuy nhiên có thể yêu cầu bạn thực hiện một số bước migration.
1. Cập nhật tên class và method của observer.
2. Cập nhật tên method của fallback paywalls.
3. Cập nhật tên class view trong các method xử lý sự kiện.
## Cập nhật tên class và method của observer \{#update-observer-class-and-method-names\}
Tên class observer và method đăng ký của nó đã được đổi tên:
```diff
- import com.adapty.kmp.AdaptyUIObserver
+ import com.adapty.kmp.AdaptyUIPaywallsEventsObserver
- import com.adapty.kmp.models.AdaptyUIView
+ import com.adapty.kmp.models.AdaptyUIPaywallView
- class MyAdaptyUIObserver : AdaptyUIObserver {
- override fun paywallViewDidPerformAction(view: AdaptyUIView, action: AdaptyUIAction) {
+ class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver {
+ override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) {
// handle actions
}
}
// Set up the observer
- AdaptyUI.setObserver(MyAdaptyUIObserver())
+ AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver())
```
## Cập nhật tên method của fallback paywalls \{#update-fallback-paywalls-method-name\}
Tên method dùng để thiết lập fallback paywalls đã được thay đổi:
```diff showLineNumbers
- Adapty.setFallbackPaywalls(assetId = "fallback.json")
+ Adapty.setFallback(assetId = "fallback.json")
.onSuccess {
// Fallback paywalls loaded successfully
}
.onError { error ->
// Handle the error
}
```
## Cập nhật tên class view trong các method xử lý sự kiện \{#update-view-class-name-in-event-handling-methods\}
Tất cả các method xử lý sự kiện hiện sử dụng class `AdaptyUIPaywallView` mới thay vì `AdaptyUIView`:
```diff
- override fun paywallViewDidAppear(view: AdaptyUIView) {
+ override fun paywallViewDidAppear(view: AdaptyUIPaywallView) {
// Handle paywall appearance
}
- override fun paywallViewDidDisappear(view: AdaptyUIView) {
+ override fun paywallViewDidDisappear(view: AdaptyUIPaywallView) {
// Handle paywall disappearance
}
- override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) {
+ override fun paywallViewDidSelectProduct(view: AdaptyUIView, productId: String) {
// Handle product selection
}
- override fun paywallViewDidStartPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct) {
+ override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) {
// Handle purchase start
}
- override fun paywallViewDidFinishPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) {
+ override fun paywallViewDidFinishPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) {
// Handle purchase result
}
- override fun paywallViewDidFailPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct, error: AdaptyError) {
+ override fun paywallViewDidFailPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, error: AdaptyError) {
// Add your purchase failure handling logic here
}
- override fun paywallViewDidFinishRestore(view: AdaptyUIView, profile: AdaptyProfile) {
+ override fun paywallViewDidFinishRestore(view: AdaptyUIPaywallView, profile: AdaptyProfile) {
// Add your successful restore handling logic here
}
- override fun paywallViewDidFailRestore(view: AdaptyUIView, error: AdaptyError) {
+ override fun paywallViewDidFailRestore(view: AdaptyUIPaywallView, error: AdaptyError) {
// Add your restore failure handling logic here
}
- override fun paywallViewDidFinishWebPaymentNavigation(view: AdaptyUIView, product: AdaptyPaywallProduct?, error: AdaptyError?) {
+ override fun paywallViewDidFinishWebPaymentNavigation(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct?, error: AdaptyError?) {
// Handle web payment navigation result
}
- override fun paywallViewDidFailLoadingProducts(view: AdaptyUIView, error: AdaptyError) {
+ override fun paywallViewDidFailLoadingProducts(view: AdaptyUIPaywallView, error: AdaptyError) {
// Add your product loading failure handling logic here
}
- override fun paywallViewDidFailRendering(view: AdaptyUIView, error: AdaptyError) {
+ override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) {
// Handle rendering error
}
```
---
# End of Documentation
_Generated on: 2026-06-24T14:36:38.787Z_
_Successfully processed: 50/50 files_
# REACT-NATIVE - Adapty Documentation (Full Content)
This file contains the complete content of all documentation pages for this platform.
Locale: vi
Generated on: 2026-06-24T14:36:38.789Z
Total files: 44
---
# File: sdk-installation-react-native-expo
---
---
title: "Cài đặt & cấu hình Adapty React Native SDK trong dự án Expo"
description: "Hướng dẫn từng bước cài đặt Adapty React Native SDK trong dự án Expo cho ứng dụng dựa trên gói đăng ký."
---
:::important
Hướng dẫn này đề cập đến việc cài đặt và cấu hình Adapty React Native SDK **trong dự án Expo**.
Nếu bạn đang sử dụng **pure React Native (không dùng Expo)**, hãy làm theo [hướng dẫn cài đặt React Native](sdk-installation-react-native-pure) thay thế.
:::
Adapty SDK bao gồm hai module chính để tích hợp liền mạch vào ứng dụng React Native của bạn:
- **Core Adapty**: Module này bắt buộc để Adapty hoạt động đúng trong ứng dụng của bạn.
- **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 với người dùng để dễ dàng tạo paywall đa nền tảng. AdaptyUI được kích hoạt tự động cùng với module core.
Nếu bạn cần hướng dẫn đầy đủ về cách triển khai IAP trong ứng dụng React Native, hãy xem [bài viết này](https://adapty.io/blog/react-native-in-app-purchases-tutorial/).
:::tip
Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng Expo? Hãy xem các ứng dụng mẫu của chúng tôi:
- [Expo dev build sample](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) cho đầy đủ chức năng bao gồm mua hàng thực và Paywall Builder
- [Expo Go & Web sample](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) để kiểm thử với chế độ mock
:::
Để xem hướng dẫn triển khai đầy đủ, bạn cũng có thể xem video:
### Trong quá trình đăng nhập/đăng ký \{#during-loginsignup\}
Nếu bạn đang 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 sử dụng customer user ID này trước đây**, Adapty sẽ tự động liên kết nó với hồ sơ 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ơ đượ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 người.
:::
Luôn `await` `identify` trước khi gọi các phương thức SDK khác. Các lần gọi đồng thời sẽ tạo ra `#3006 profileWasChanged` hoặc rơi vào hồ sơ ẩn danh. Xem [Thứ tự gọi trong React Native SDK](react-native-sdk-call-order).
```typescript showLineNumbers
try {
await adapty.identify("YOUR_USER_ID"); // Unique for each user
// successfully identified
} catch (error) {
// handle the error
}
```
### 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ơ ẩn danh mới và chỉ chuyển sang hồ sơ hiện có sau khi bạn gọi `identify`.
Bạn có thể truyền customer user ID hiện có (cái bạn đã sử dụng trước đây) hoặc một cái mới. Nếu bạn truyền một cái mới, hồ sơ mới được tạo khi kích hoạt sẽ tự động được liên kết với customer user ID đó.
:::note
Theo mặc định, việc tạo hồ sơ ẩn danh không ảnh hưởng đến các dashboard analytics, vì lượt cài đặt được đếm dựa trên device ID.
Device ID đạ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ơ (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 ra các sự kiện cài đặt bổ sung.
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).
:::
```typescript showLineNumbers
adapty.activate("PUBLIC_SDK_KEY", {
customerUserId: "YOUR_USER_ID" // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one.
});
```
### Đăng xuất người dùng \{#log-users-out\}
Nếu bạn có nút để đăng xuất người dùng, hãy sử dụng phương thức `logout`.
:::important
Đăng xuất người dùng sẽ tạo một hồ sơ ẩn danh mới cho người dùng đó.
:::
```typescript showLineNumbers
try {
await adapty.logout();
// successful logout
} catch (error) {
// handle the error
}
```
:::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 không cần đăng nhập \{#allow-purchases-without-login\}
Nếu người dùng của bạn có thể mua hàng 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ơ ẩ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ơ đã 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ơ hiện tại, vì vậy toàn bộ lịch sử giao dịch mua được duy trì.
- Nếu đây là customer user ID đã tồn tại (customer user ID đã được liên kết với một hồ sơ), bạn cần lấy mức độ truy cập thực tế sau khi chuyển đổi hồ sơ. Bạn có thể gọi [`getProfile`](react-native-check-subscription-status) ngay sau khi xác định, hoặc [lắng nghe các cập nhật hồ sơ](react-native-check-subscription-status) để dữ liệu tự động đồng bộ.
## 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ốt hơn nữa, 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
- [**Onboarding**](react-native-onboardings): Thu hút người dùng với onboarding và thúc đẩy giữ chân người dùng
- [**Tích hợp**](configuration): Tích hợp với các dịch vụ attribution marketing và analytics chỉ trong một dòng code
- [**Đặt thuộc tính hồ sơ tùy chỉnh**](react-native-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 nhóm người dùng khác nhau
---
# File: adapty-sdk-integration-skill-react-native
---
---
title: "Tích hợp Adapty vào ứng dụng React Native với kỹ năng tích hợp SDK"
description: "Sử dụng kỹ năng adapty-sdk-integration để tích hợp Adapty SDK vào ứng dụng React Native của bạn từ đầu đến cuối với công cụ AI coding."
---
:::important
Kỹ năng 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-react-native) — 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-react-native
---
---
title: "Tích hợp Adapty vào ứng dụng React Native 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 React Native của bạn bằng Cursor, Context7, ChatGPT, Claude hoặc các công cụ AI khác."
---
Hướng dẫn này sẽ dẫn bạn qua từng bước tích hợp Adapty vào ứng dụng React Native của bạn với sự hỗ trợ của 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 trên 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ỳ mã SDK nào. Bạn có thể thực hiện điều này thông qua một skill LLM tương tác, hoặc thủ công qua Dashboard.
### Cách dùng Skill (khuyến nghị) \{#skill-approach-recommended\}
Skill Adapty CLI 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 — kể cả khi nào cần mở Dashboard để kết nối cửa hàng.
### Cách thiết lập thủ công trên Dashboard \{#dashboard-approach\}
Nếu bạn muốn tự cấu hình mọi thứ, dưới đây là những gì bạn cần trước khi viết bất kỳ mã nào. LLM của bạn không thể tra cứu các giá trị từ dashboard — bạn sẽ phải cung cấp chúng.
1. **Kết nối cửa hàng ứng dụng của bạn**: Trong Adapty Dashboard, vào **App settings → General**. Kết nối cả App Store và Google Play nếu ứng dụng của bạn nhắm đến cả hai nền tảng. Đây là điều kiện 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 của bạn**: Trong Adapty Dashboard, vào **App settings → General**, sau đó tìm phần **API keys**. Trong mã, đây là chuỗi bạn truyền vào `adapty.activate("YOUR_PUBLIC_SDK_KEY")`.
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 mã — Adapty phân phối chúng thông qua paywall.
[Thêm sản phẩm](quickstart-products)
4. **Tạo một paywall và một placement**: Trong Adapty Dashboard, tạo paywall trên trang **Paywalls**, sau đó gán nó cho một placement trên trang **Placements**. Trong mã, 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 theo từng sản phẩm trên trang **Products**. Trong mã, đây là 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 mã.
:::tip
Khi bạn có đủ năm mục trên, bạn đã sẵn sàng viết mã. Hãy cho LLM của bạn biết: "Public SDK key của tôi là X, placement ID của tôi là Y" để nó có thể tạo mã 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 mã, 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 mã.
[A/B test](ab-tests)
- **Thêm paywall và placement**: Thêm nhiều lời 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 tùy theo từng 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 cho phép LLM của bạn truy cập trực tiếp vào tài liệu Adapty luôn cập nhật. LLM của bạn sẽ tự động lấy đúng tài liệu dựa trên câu hỏi của bạn — 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 [repository GitHub của Context7](https://github.com/upstash/context7).
Sau khi cấu hình xong, tham chiếu thư viện Adapty trong prompt của bạn:
```
Use the adaptyteam/adapty-docs library to look up how to install the React Native 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 rất 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 đúng.
:::
### Dùng tài liệu dạng văn bản thuần \{#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 của nó, hoặc nhấp **Copy for LLM** bên dưới tiêu đề bài viết. Ví dụ: [adapty-cursor-react-native.md](https://adapty.io/docs/vi/adapty-cursor-react-native.md).
Mỗi giai đoạn trong [hướng dẫn triển khai](#implementation-walkthrough) bên dưới đều có một khối "Gửi cho LLM của bạn" với các link `.md` để dán vào.
Để có thêm tài liệu cùng 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 sẽ đi qua việc tích hợp Adapty theo đúng thứ tự triển khai. Mỗi giai đoạn bao gồm tài liệu cần gửi cho LLM, kết quả mong đợi khi hoàn thành và các vấn đề thường gặp.
### Lập kế hoạch tích hợp \{#plan-your-integration\}
Trước khi bắt đầu viết mã, hãy yêu cầu LLM của bạn phân tích dự á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 của bạn lẫn tài liệu Adapty trước khi viết bất kỳ mã nào.
Hãy cho LLM biết cách bạn xử lý mua hàng — điều này ảnh hưởng đến các hướng dẫn nó cầ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 sẽ hiển thị chúng tự động.
- [**Paywall tự tạo**](react-native-making-purchases): Bạn tự xây dựng giao diện paywall trong mã 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 cơ sở hạ tầng mua hàng hiện có và chỉ dùng Adapty cho analytics và tích hợp.
Chưa chắc nên chọn cái nào? Đọc [bảng so sánh trong quickstart](react-native-quickstart-paywalls).
### Cài đặt và cấu hình SDK \{#install-and-configure-the-sdk\}
Thêm dependency Adapty SDK bằng npm (hoặc yarn) 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.
Chúng tôi có hướng dẫn cài đặt riêng cho dự án Expo và bare React Native — hãy chọn cái phù hợp với thiết lập của bạn.
**Hướng dẫn:**
- [Cài đặt với Expo](sdk-installation-react-native-expo)
- [Cài đặt với bare React Native](sdk-installation-react-native-pure)
Gửi cho LLM của bạn (chọn cái phù hợp với thiết lập của bạn, hoặc gửi cả hai):
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/vi/sdk-installation-react-native-expo.md
- https://adapty.io/docs/vi/sdk-installation-react-native-pure.md
```
:::tip[Kiểm tra]
- **Kết quả mong đợi:** Ứng dụng build và chạy được trên cả iOS và Android. Log của Metro bundler 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. Các hướng dẫn bạn cần phụ thuộc vào cách bạn xử lý mua hàng.
Hãy kiểm tra từng giao dịch trong sandbox khi thực hiện — đừng đợi đế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.
tùy chọn
mặc định: `en`
|Đị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ữ gồm một hoặc hai thẻ phụ được phân tách bằng dấu trừ (**-**). Thẻ phụ đầu tiên là ngôn ngữ, thẻ phụ thứ hai là khu vực.
Ví dụ: `en` là tiếng Anh, `pt-br` là 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ố tải dữ liệu từ server và trả về dữ liệu đã cache trong trường hợp thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng 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 thường xuyên gặp 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 hoàn toàn, 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ọ như thế nào. Cache được cập nhật thường xuyên nên an toàn để sử dụng trong phiên nhằm tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn nguyên sau 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 dọn dẹp thủ công.
Adapty SDK lưu trữ paywalls 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 paywalls 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 đượ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 paywalls trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet kém.
| | **loadTimeoutMs** | 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 được đạt đến, 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ể 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 các yêu cầu khác nhau bên dưới.
Với Android: Bạn có thể tạo `TimeInterval` với các extension function (như `5.seconds`, trong đó `.seconds` là từ `import com.adapty.utils.seconds`), hoặc `TimeInterval.seconds(5)`. Để không giới hạn, sử dụng `TimeInterval.INFINITE`.
| Tham số phản hồi: | Tham số | Mô tả | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Một đối tượng [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) với 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 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 Đả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 có sẵn để truy xuất. ::: Sau khi lấy paywall, hãy kiểm tra xem nó có bao gồm `ViewConfiguration` hay 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ư một Paywall Builder paywall; nếu không, [xử lý nó như một remote config paywall](present-remote-config-paywalls-react-native). Trong React Native 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 theo cách 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` lần nữa. Gọi hai lần mà không tạo lại có thể dẫn đến lỗi `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers // for the Adapty SDK < 3.14 – import {createPaywallView} from 'react-native-adapty/dist/ui'; if (paywall.hasViewConfiguration) { try { const view = await createPaywallView(paywall); } catch (error) { // handle the error } } else { //use your custom logic } ``` 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. | | **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ể để cá nhân hóa nội dung trong paywall. Tham khảo chủ đề Custom tags in paywall builder để biết thêm chi tiết. | | **prefetchProducts** | tùy chọn | Bật để tối ưu hóa thời gian hiển thị sản phẩm trên màn hình. Khi `true`, AdaptyUI sẽ tự động lấy các sản phẩm cần thiết. Mặc định: `false`. | :::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](react-native-localizations-and-locale-codes). ::: Sau khi có view, hãy [hiển thị paywall](react-native-present-paywalls). ## Lấy paywall cho đối tượng mặc định để tải nhanh hơn \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Thông thường, paywalls được tải 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à paywalls, và người dùng của bạn có kết nối internet yếu, việc tải 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. Để 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 thông tin Paywall](#fetch-paywall-designed-with-paywall-builder) ở trên. :::warning Lý do chúng tôi khuyến nghị sử dụng `getPaywall` Phương thức `getPaywallForDefaultAudience` có một số nhược điểm đáng kể: - **Vấn đề khả năng tương thích ngược tiềm ẩn**: Nếu bạn cần hiển thị các paywalls khác nhau cho các phiên bản ứng dụng khác nhau (hiện tại và tương lai), bạn có thể gặp khó khăn. Bạn sẽ phải thiết kế paywalls hỗ trợ phiên bản hiện tại (cũ) hoặc chấp nhận rằng người dùng có phiên bản hiện tại (cũ) có thể gặp sự cố với paywalls không được render. - **Mất khả năng nhắm mục tiêu**: Tất cả người dùng sẽ thấy cùng một paywall được thiết kế cho đối tượng **All Users**, nghĩa là bạn mất khả năng nhắm mục tiêu cá nhân hóa (bao gồm theo quốc gia, marketing attribution hoặc các thuộc tính tùy chỉnh của riêng bạn). Nếu bạn sẵn sàng chấp nhận những nhược điểm này để hưởng lợi từ việc tải paywall nhanh hơn, hãy sử dụng phương thức `getPaywallForDefaultAudience` như sau. Nếu không, hãy sử dụng `getPaywall` được mô tả [ở trên](#fetch-paywall-designed-with-paywall-builder). ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywallForDefaultAudience(id, locale); // the requested paywall } catch (error) { // handle the error } ``` :::note Phương thức `getPaywallForDefaultAudience` có sẵn từ React Native SDK phiên bản 2.11.2 trở lên. ::: | 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 của bạn. | | **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 được kỳ vọng là mã ngôn ngữ gồm một hoặc nhiều thẻ phụ được phân tách bằng dấu trừ (**-**). Thẻ phụ đầu tiên là ngôn ngữ, thẻ phụ thứ hai là khu vực.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha Brazil.
Xem [Bản địa hóa và mã locale](react-native-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 đã cache trong trường hợp thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng 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 thường xuyên gặp 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 hoàn toàn, 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ọ như thế nào. Cache được cập nhật thường xuyên nên an toàn để sử dụng trong phiên nhằm tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn nguyên sau 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 dọn dẹp thủ công.
| ## 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 xác định trước: `hero_image` và `hero_video`. Trong một custom asset bundle, bạn nhắm mục tiêu 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ị hình ảnh preview cục bộ trong khi hình ảnh chính từ xa đang tải. - Hiển thị hình ảnh preview trước khi chạy video. :::important Để sử dụng tính năng này, hãy cập nhật Adapty React Native SDK lên phiên bản 3.8.0 trở lên. ::: Đâ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: ```javascript const customAssets: Record
## Số lượt xem paywall bị nhân đôi \{#the-paywall-view-number-is-too-big\}
**Sự cố**: Số lượt xem paywall hiển thị gấp đôi so với dự kiến.
**Nguyên nhân**: Có thể bạn đang gọi `logShowPaywall` trong code, khiến số lượt xem bị tính hai lần 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\}
**Sự cố**: Bạn gặp phải các vấn đề liên quan đến Paywall Builder chưa đượ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](react-native-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: react-native-quickstart-manual
---
---
title: "Bật tính năng mua hàng trong paywall tùy chỉnh với React Native SDK"
description: "Tích hợp Adapty SDK vào các paywall React Native tùy chỉnh để bật 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 giữ 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 triển khai 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 sử dụng [Adapty Paywall Builder](react-native-quickstart-paywalls). Với Paywall Builder, bạn tạo paywall trong 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 in-app purchase, bạn cần hiểu ba khái niệm chính:
- [**Sản phẩm**](product) – bất cứ thứ gì người dùng có thể mua (gói đăng ký, consumable, quyền truy cập trọn đời)
- [**Paywalls**](paywalls) – 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 chỉnh sửa sản phẩm, giá cả và ưu đãi mà không cần thay đổi code ứng dụng.
- [**Placements**](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, rồi truy xuất 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 làm việc với paywall tùy chỉnh. Về cơ bản, đây chỉ 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 vào một **placement**. Thiết lập này cho phép bạn lấy sản phẩm của mình. Để hiểu những gì cần làm trên dashboard, hãy làm theo 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ần xác thực backend ở phía mình.
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](react-native-quickstart-identify) để hiểu 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, 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`.
```typescript showLineNumbers
async function loadPaywall() {
try {
const paywall: AdaptyPaywall = await adapty.getPaywall('YOUR_PLACEMENT_ID');
const products: AdaptyPaywallProduct[] = await adapty.getPaywallProducts(paywall);
// Use products to build your custom paywall UI
} catch (error) {
// Handle the error
}
}
```
## 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, hãy gọi phương thức `makePurchase` với sản phẩm đã chọn. Phương thứ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.
```typescript showLineNumbers
async function purchaseProduct(product: AdaptyPaywallProduct) {
try {
const purchaseResult: AdaptyPurchaseResult = await adapty.makePurchase(product);
switch (purchaseResult.type) {
case 'success':
// Purchase successful, profile updated
break;
case 'user_cancelled':
// User canceled the purchase
break;
case 'pending':
// Purchase is pending (e.g., user will pay offline with cash)
break;
}
} catch (error) {
// Handle the error
}
}
```
## 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 có thể 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. Phương thứ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.
```typescript showLineNumbers
async function restorePurchases() {
try {
const profile: AdaptyProfile = await adapty.restorePurchases();
// Restore successful, profile updated
} catch (error) {
// Handle the error
}
}
```
## Các bước tiếp theo \{#next-steps\}
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
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 là một mã ngôn ngữ gồm một hoặc nhiều thẻ phụ được phân tách bằng dấu trừ (**-**). Thẻ đầu tiên là ngôn ngữ, thẻ thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha của Brazil.
Xem [Localizations and locale codes](react-native-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ừ máy chủ và trả về dữ liệu đã lưu trong cache nếu 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 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 thường xuyên gặp 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 thời gian tải sẽ nhanh hơn dù kết nối internet không tốt. Cache được cập nhật thường xuyên nên hoàn toàn an toàn khi sử dụng trong phiên làm việc để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt ứng dụng hoặc xóa thủ công.
Adapty SDK lưu trữ paywalls theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và [paywall dự phòng](react-native-use-fallback-paywalls). Chúng tôi cũng sử dụng CDN để lấy paywalls 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. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản paywall mới nhất trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet yếu.
| | **loadTimeoutMs** | 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 `loadTimeout` đã chỉ định, vì thao tác có thể bao gồm nhiều yêu cầu khác nhau bên dưới.
| Đừng hardcode product ID! Vì paywalls được cấu hình từ xa, các sản phẩm có sẵn, số lượng sản phẩm và ư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 nhận được 2 sản phẩm, ứng dụng của bạn nên hiển thị 2 sản phẩm đó. Nhưng nếu sau này bạn nhận được 3 sản phẩm, ứng dụng của bạn nên hiển thị cả 3 mà không cần thay đổi code. Điều duy nhất bạn phải hardcode là placement ID. Tham số trả về: | Tham số | Mô tả | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Một đối tượng [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) với: danh sách product ID, đị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ó: ```typescript showLineNumbers try { // ...paywall const products = await adapty.getPaywallProducts(paywall); // the requested products list } catch (error) { // handle the error } ``` Tham số trả về: | Tham số | Mô tả | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Danh sách các đối tượng [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) với: định danh sản phẩm, tên sản phẩm, giá, đơn vị tiền tệ, thời hạn gói đăng ký và một số thuộc tính khác. | Khi tự thiết kế paywall, bạn sẽ cần truy cập các thuộc tính này từ đối tượng [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct). 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 để xem đầy đủ tất cả các thuộc tính có sẵn. | Thuộc tính | Mô tả | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Để hiển thị tiêu đề sản phẩm, sử dụng `product.localizedTitle`. Lưu ý rằng việc 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ị giá đã được bản địa hóa, sử dụng `product.price?.localizedString`. Việc bản địa hóa này dựa trên thông tin locale 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ệ liên quan, 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?.localizedSubscriptionPeriod`. Việc bản địa hóa này dựa trên locale của thiết bị. Để lấy chu kỳ gói đăng ký theo cách lập trình, sử dụng `product.subscription?.subscriptionPeriod`. Từ đó bạn có thể truy cập thuộc tính `unit` để lấy độ dài (tức là 'day', 'week', 'month', 'year', hoặc 'unknown'). Giá trị `numberOfUnits` sẽ cho bạn biết số đơn vị chu kỳ. Ví dụ: với gói đăng ký theo quý, bạn sẽ thấy `'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 biết 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á ưu đãi giới thiệu. Trong mỗi đối tượng giai đoạn có các thuộc tính hữu ích sau: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 là một mã ngôn ngữ gồm một hoặc nhiều thẻ phụ được phân tách bằng dấu trừ (**-**). Thẻ đầu tiên là ngôn ngữ, thẻ thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha của Brazil.
Xem [Localizations and locale codes](react-native-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ừ máy chủ và trả về dữ liệu đã lưu trong cache nếu 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 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 thường xuyên gặp 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 thời gian tải sẽ nhanh hơn dù kết nối internet không tốt. Cache được cập nhật thường xuyên nên hoàn toàn an toàn khi sử dụng trong phiên làm việc để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt ứng dụng hoặc xóa thủ công.
| --- # File: present-remote-config-paywalls-react-native --- --- title: "Render paywall designed by remote config in React Native SDK" description: "Khám phá cách hiển thị paywall Remote Config trong Adapty React Native 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 render trong code của ứng dụng để hiển thị nó cho người dùng. Vì Remote Config mang lại sự linh hoạt theo nhu cầu của bạn, bạn hoàn toàn 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 một phương thức để lấy cấu hình remote, giúp bạn tự do hiển thị paywall tùy chỉnh đã được thiết lập 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, hãy truy cập thuộc tính `remoteConfig` và trích xuất các giá trị cần thiết. ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: "YOUR_PLACEMENT_ID" }); const headerText = paywall.remoteConfig?.data?.["header_text"]; } catch (error) { // handle the error } ``` 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 thành một trang đẹ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, mang lại trải nghiệm mượt mà và thân thiện trên mọi thiết bị. :::warning Hãy đảm bảo [ghi lại sự kiện xem paywall](present-remote-config-paywalls-react-native#track-paywall-view-events) như mô tả bên dưới, để Adapty analytics có thể thu thập dữ liệu cho các 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 luồng 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](react-native-making-purchases). Chúng tôi khuyến nghị [tạo một paywall dự phòng](react-native-use-fallback-paywalls). Paywall dự phòng này sẽ được hiển thị cho người dùng khi không có kết nối internet hoặc cache, đảm bảo trải nghiệm mượt mà ngay cả trong những tình huống đó. ## Theo dõi sự kiện xem paywall \{#track-paywall-view-events\} Adapty hỗ trợ bạn đo lường hiệu quả của các paywall. Trong khi dữ liệu mua hàng được thu thập tự độ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 thực sự thấy paywall. Để ghi lại sự kiện xem paywall, chỉ cần gọi `.logShowPaywall(paywall)`, và sự kiện này sẽ được phản ánh trong các chỉ số 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). ::: ```typescript showLineNumbers await adapty.logShowPaywall(paywall); ``` Tham số yêu cầu: | Tham số | Bắt buộc | Mô tả | | :---------- | :------- |:--------------------------------------------------------------------------------------------| | **paywall** | bắt buộc | Một đối tượng [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall). | --- # File: react-native-making-purchases --- --- title: "Thực hiện mua hàng trong ứng dụng với React Native 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ỉ cần hiển thị paywall là đủ để hỗ trợ mua hàng nếu bạn dùng [Paywall Builder](adapty-paywall-builder) để tùy chỉnh paywall của mình. Nếu không dùng Paywall Builder, bạn phải 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. Phương thức này là cổng để người dùng tương tác với paywall và tiến hành giao dịch. Nếu paywall của bạn có ưu đãi đang hoạt động cho sản phẩm mà người dùng 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 bằng Paywall Builder. Trong các trường hợp khác, bạn cần [xác minh tính đủ đ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ể dẫn đến ứng dụng bị từ chối khi phát hành. Ngoài ra, điều này có thể khiến người dùng đủ điều kiện nhận ưu đãi giới thiệu bị tính giá đầy đủ. ::: Hãy đảm bảo bạn đã [hoàn thành cấu hình ban đầu](quickstart) mà không bỏ qua bất kỳ bước nào. Nếu không, chúng tôi không thể xác thực giao dịch mua hàng. ## Thực hiện mua hàng \{#make-purchase\} :::note **Đang dùng [Paywall Builder](adapty-paywall-builder)?** Các giao dịch mua được xử lý tự động — bạn có thể bỏ qua bước này. **Muốn có hướng dẫn từng bước?** Xem [hướng dẫn quickstart](react-native-implement-paywalls-manually) để có hướng dẫn triển khai đầy đủ từ đầu đến cuối. ::: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` Tham số yêu cầu: | Tham số | Bắt buộc | Mô tả | | :---------- | :------- |:-------------------------------------------------------------------------------------------------------------------------------| | **Product** | bắt buộc | Đối tượng [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) 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 chứa đối tượng này. Đối tượng [AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile) cung cấp thông tin toàn diện về mức độ truy cập, gói đăng ký và 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 2.0 và Adapty SDK phiên bản thấp hơn 2.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 xử lý phụ thuộc vào cửa hàng: - Với App Store, gói đăng ký sẽ tự động được cập nhật trong cùng nhóm gói đăng ký. Nếu người dùng mua một gói từ nhóm này trong khi đã có gói từ nhóm khác, cả hai gói đều sẽ hoạt động đồng thời. - Với Google Play, gói đăng ký không tự động được cập nhật. Bạn cần xử 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 gói khác trên Android, gọi phương thức `.makePurchase()` với tham số bổ sung: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product, params); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` Tham số yêu cầu bổ sung: | Tham số | Bắt buộc | Mô tả | | :--------- | :------- | :----------------------------------------------------------- | | **params** | bắt buộc | Đối tượng thuộc kiểu [`MakePurchaseParamsInput`](https://react-native.adapty.io/types/makepurchaseparamsinput). | :::info **Phiên bản 3.8.2+**: Cấu trúc `MakePurchaseParamsInput` đã được cập nhật. `oldSubVendorProductId` và `prorationMode` giờ nằm trong `subscriptionUpdateParams`, và `isOfferPersonalized` được chuyển lên cấp trên. Ví dụ: ```javascript makePurchase(product, { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } }); ``` ::: 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ề 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ý. Hạ cấp không được hỗ trợ. - 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';Đối tượng [`AdaptyProfile`](https://react-native.adapty.io/interfaces/adaptyprofile). 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-react-native --- --- title: "Triển khai Observer mode trong React Native 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 React Native SDK." --- Nếu bạn đã có hệ thố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 phù hợp với nhu cầu của bạn, bạn chỉ cần: 1. Bật nó 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 thiết lập cho [React Native](sdk-installation-reactnative). 2. [Báo cáo các giao dịch](report-transactions-observer-mode-react-native) từ hệ thống mua hàng hiện có của bạn tới Adapty. ### Thiết lập 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ý, và chỉ dùng Adapty để gửi 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 đó. ::: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { observerMode: true, // Enable observer mode }); ``` Các tham số: | Tham số | Mô tả | | --------------------------- | ------------------------------------------------------------ | | observerMode | Giá trị boolean điều khiển [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 các 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ố thiết lập trong Observer mode. Đây là những gì bạn cần thực hiện ngoài các bước trên: 1. Hiển thị paywall như bình thường cho [remote config paywalls](present-remote-config-paywalls-react-native). 3. [Liên kết paywall](report-transactions-observer-mode-react-native) với các giao dịch mua hàng. --- # File: report-transactions-observer-mode-react-native --- --- title: "Báo cáo giao dịch trong Observer Mode trên React Native 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 trên React Native SDK." ---Với iOS, StoreKit 1: đối tượng [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).
Với iOS, StoreKit 2: đối tượng [Transaction](https://developer.apple.com/documentation/storekit/transaction).
Với Android: Định danh chuỗi (purchase.getOrderId của giao dịch mua hàng, 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 | Định danh chuỗi của biến thể. Bạn có thể lấy giá trị này qua thuộc tính `variationId` của đối tượng [AdaptyPaywall](https://react-native.adapty.io/interfaces/adaptypaywall). |phoneNumber
firstName
lastName
| String | | gender | Enum, các giá trị cho phép là: `female`, `male`, `other` | | birthday | Date | ### Thuộc tính tùy chỉnh của người dùng \{#custom-user-attributes\} Bạn có thể thiết lập 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 sử dụng ứng dụng. Ví dụ, với ứng dụng thể dục, chúng có thể là số buổi tập mỗi tuần; với ứng dụng học ngôn ngữ, chúng có thể là trình độ kiến thức 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 analytics để tìm ra chỉ số sản phẩm nào ảnh hưởng nhiều nhất đến doanh thu. ```typescript showLineNumbers try { await adapty.updateProfile({ codableCustomAttributes: { key_1: 'value_1', key_2: 2, }, }); } catch (error) { // handle `AdaptyError` } ``` Để xóa một key hiện có, sử dụng phương thức `.withRemoved(customAttributeForKey:)`: ```typescript showLineNumbers try { // to remove a key, pass null as its value await adapty.updateProfile({ codableCustomAttributes: { key_1: null, key_2: null, }, }); } catch (error) { // handle `AdaptyError` } ``` Đô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 sử 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ừ nhiều thiết bị khác nhau vào bất kỳ lúc nào, nên các thuộc tính trên server có thể đã thay đổi sau lần đồng bộ 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 dài 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 bất kỳ ký tự nào sau đây: `_` `-` `.` - Giá trị có thể là chuỗi hoặc số thực (float) với tối đa 50 ký tự. --- # File: react-native-listen-subscription-changes --- --- title: "Kiểm tra trạng thái gói đăng ký trong React Native 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 khả năng giữ chân khách hàng trong ứng dụng React Native của bạn." --- Với Adapty, việc theo dõi trạng thái gói đăng ký trở nên rất đơn giản. Bạn không cần phải chèn thủ công các 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.Một đối tượng [AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile). 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 xem 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ả mới nhất vì nó luôn cố gắng 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ừ bộ nhớ cache sẽ được trả về. Cần lưu ý rằng Adapty SDK cập nhật cache của `AdaptyProfile` thường xuyên để giữ thông tin này luôn cập nhật nhất có thể.
| Phương thức `.getProfile()` cung cấp 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 trong một ứ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". Nhưng hầu hết thời gian, bạn chỉ cần một mức độ truy cập — trong trường hợp đó, bạn có thể 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: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); const isActive = profile.accessLevels?.["premium"]?.isActive; if (isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` ### 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 tin nhắn từ Adapty, bạn cần thực hiện một số cấu hình bổ sung: ```typescript showLineNumbers // Create an "onLatestProfileLoad" event listener adapty.addEventListener('onLatestProfileLoad', 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ể yêu cầu 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: react-native-deal-with-att --- --- title: "Xử lý ATT trong React Native SDK" description: "Bắt đầu với Adapty trên React Native để đơ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 cho 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/) đến Adapty. ```typescript showLineNumbers try { await adapty.updateProfile({ // you can also pass a string value (validated via tsc) if you prefer appTrackingTransparencyStatus: AppTrackingTransparencyStatus.Authorized, }); } catch (error) { // handle `AdaptyError` } ``` :::warning 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 chuyển đến các tích hợp bạn đã cấu hình một cách kịp thời. ::: --- # File: kids-mode-react-native --- --- title: "Chế Độ Trẻ Em trong React Native SDK" description: "Dễ dàng bật Chế Độ Trẻ Em để 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 React Native SDK." --- Nếu ứng dụng React Native của bạn dành cho trẻ em, bạn phải tuân theo 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 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. ## Cần làm gì? \{#whats-required\} Bạn cần cấu hình Adapty SDK để tắt việc 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ị sử dụng customer user ID một cách thận trọng. ID người dùng có định dạng `tùy chọn
mặc định: `en`
|Định danh của bản dịch onboarding. Tham số này phải là mã ngôn ngữ gồm một hoặc hai subtag phân cách bằng dấu trừ (**-**). Subtag đầu tiên là ngôn ngữ, subtag thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).
Xem [Localizations and locale codes](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.
| | **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 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 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 dù kết nối mạng không ổn định. Cache được cập nhật thường xuyên, vì vậy có thể dùng nó trong phiên để tránh các request mạng.
Lưu ý rằng cache vẫn tồn tại khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt ứng dụng hoặc thông qua việc dọn dẹp thủ công.
Adapty SDK lưu trữ onboarding cục bộ trong 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 để tải onboarding nhanh hơn và một server dự phòng độc lập trong trường hợp CDN không 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 onboarding mới nhất trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet hạn chế.
| | **loadTimeoutMs** | 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`, do thao tác có thể bao gồm nhiều request khác nhau bên dưới.
| Các tham số trả về: | Tham số | Mô tả | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Một đối tượng [`AdaptyOnboarding`](https://react-native.adapty.io/interfaces/adaptyonboarding) gồm: định danh và cấu hình onboarding, Remote Config, và một số thuộc tính khác. | ## 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ó kết nối internet yếu, việc lấy onboarding có thể mất nhiều thời gian hơn bạn muốn. Trong những tình huống đó, 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. Để giải quyết vấn đề 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 đã 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ư đã mô tả trong phần [Lấy onboarding](#fetch-onboarding) ở trên. :::warning Hãy cân nhắc 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ể gây ra sự cố 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á nhân hóa**: Chỉ hiển thị nội dung cho đối tượng "All Users", bỏ qua 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 tốc độ lấy nhanh hơn vượt trội hơn những hạn chế 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). ::: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const onboarding = await adapty.getOnboardingForDefaultAudience(placementId, locale); // the requested onboarding } catch (error) { // handle the error } ``` Các 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 dịch onboarding. Tham số này phải là mã ngôn ngữ gồm một hoặc hai subtag phân cách bằng dấu trừ (**-**). Subtag đầu tiên là ngôn ngữ, subtag thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).
Xem [Localizations and locale codes](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.
| | **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 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 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 dù kết nối mạng không ổn định. Cache được cập nhật thường xuyên, vì vậy có thể dùng nó trong phiên để tránh các request mạng.
Lưu ý rằng cache vẫn tồn tại khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt ứng dụng hoặc thông qua việc dọn dẹp thủ công.
Adapty SDK lưu trữ onboarding cục bộ trong 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 để tải onboarding nhanh hơn và một server dự phòng độc lập trong trường hợp CDN không 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 onboarding mới nhất trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet hạn chế.
| --- # File: react-native-present-onboardings --- --- title: "Hiển thị onboarding trong React Native SDK" description: "Khám phá cách hiển thị onboarding trên React Native để tăng tỷ lệ chuyển đổi và doanh thu." --- 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 ứng dụng di động để hiển thị cho người dùng. Onboarding đó chứa cả nội dung cần hiển thị lẫn 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 React Native SDK](sdk-installation-reactnative) phiên bản 3.8.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). Adapty React Native SDK cung cấp hai cách để hiển thị onboarding: - **React component**: Embedded component cho phép bạn tích hợp vào kiến trúc và hệ thống điều hướng của ứng dụng. - **Modal presentation** ## React component \{#react-component\} Để nhúng một onboarding vào cây component hiện có của bạn, hãy sử dụng component `AdaptyOnboardingView` trực tiếp trong cấu trúc phân cấp component React Native. Component nhúng này cho phép bạn tích hợp nó vào kiến trúc và hệ thống điều hướng của ứng dụng. :::note Trên Android, chúng tôi khuyến nghị cấu hình bổ sung cho `AdaptyOnboardingView` để tránh hiện tượng lỗi hiển thị trực quan. Xem [Giao diện hệ thống chồng lên nội dung onboarding trên Android](#system-ui-overlaps-onboarding-content-on-android). :::
Sau đó, bạn có thể 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** hay **Allow notifications**, event handler sẽ được kích hoạt với tham số `actionId` khớp với **Action ID** từ builder. Bạn có thể tạo ID riêng của mình, chẳng hạn như "allowNotifications".
:::important
Lưu ý rằng bạn cần tự quản lý những 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 đó.
:::
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 mục **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 trạng khả dụng của sản phẩm \{#step-4-check-product-availability\}
1. Quay lại **App Store Connect** và mở mục **Subscriptions** tương tự.
2. Nhấp vào tên nhóm gói đăng ký để xem các sản phẩm.
3. Chọn sản phẩm bạn đang kiểm tra.
4. Cuộn xuống mục **Availability** và kiểm tra rằng tất cả các quốc gia và khu vực cần thiết đều được liệt kê.
## Bước 4. Kiểm tra giá sản phẩm \{#step-5-check-product-prices\}
1. Truy cập lại mục **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 **Subscription Pricing** và mở rộng mục **Current Pricing for New Subscribers**.
5. Đảm bảo rằng 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 thanh toán ứng dụng, tài khoản ngân hàng và biểu mẫu thuế còn hiệu lực \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\}
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 rằng **Paid Apps Agreement**, **Bank Account** và **Tax forms** đều hiển thị trạng thái **Active**.
Bằng cách làm theo các bước trên, bạn sẽ có thể khắc phục 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 \{#step-6-recreate-the-product-if-its-stuck\}
Các bước 1–5 có thể đều vượt qua — 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ể đang 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 ID sản phẩm đó. Chờ tối đa 24 giờ sau khi tạo lại để thay đổi được phổ biến.
---
# File: cantMakePayments-react-native
---
---
title: "Sửa lỗi Code-1003 cantMakePayment trong React Native SDK"
description: "Giải quyết lỗi thực hiện 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-react-native-314
---
---
title: "Migrate Adapty React Native SDK to v3.14"
description: "Migrate to Adapty React Native SDK v3.14 for better performance and new monetization features."
---
Adapty React Native SDK 3.14.0 là một bản phát hành lớn với các cải tiến yêu cầu bạn thực hiện các bước migration:
- Phương thức `registerEventHandlers` đã được thay thế bằng phương thức `setEventHandlers`.
- Trong `AdaptyOnboardingView`, các event handler giờ được truyền dưới dạng các prop riêng lẻ thay vì một object `eventHandlers`
- Một kiểu import mới, đơn giản hơn đã được giới thiệu cho các UI component
- Phương thức `logShowOnboarding` đã bị xóa
- Phiên bản React Native tối thiểu đã được cập nhật lên 0.73.0
- Style trình bày iOS mặc định cho paywall và onboarding đã thay đổi từ page sheet sang full screen
## Thay thế `registerEventHandlers` bằng `setEventHandlers` \{#replace-registereventhandlers-with-seteventhandlers\}
Phương thức `registerEventHandlers` dùng để làm việc với Adapty Paywall và Onboarding Builder đã được thay thế bằng phương thức `setEventHandlers`.
Nếu bạn đang dùng Adapty Paywall Builder và/hoặc Adapty Onboarding Builder, hãy tìm `registerEventHandlers` trong code của bạn và thay thế bằng `setEventHandlers`.
Thay đổi này được thực hiện để làm rõ hơn hành vi của phương thức: Các handler giờ hoạt động lần lượt vì mỗi handler trả về `true`/`false`, và việc có nhiều handler cho một sự kiện khiến hành vi kết quả không rõ ràng.
Lưu ý rằng khi sử dụng các React component như `AdaptyOnboardingView` hoặc `AdaptyPaywallView`, bạn không cần trả về `true`/`false` từ các event handler vì bạn kiểm soát khả năng hiển thị của component thông qua state management của riêng mình. Giá trị trả về chỉ cần thiết khi trình bày màn hình modal, nơi SDK quản lý vòng đời của view.
:::important
Gọi `setEventHandlers` nhiều lần sẽ ghi đè các handler bạn cung cấp, thay thế cả handler mặc định lẫn các handler đã được đặt trước đó cho những sự kiện cụ thể đó.
:::
```diff showLineNumbers
- const unsubscribe = view.registerEventHandlers({
- // your event handlers
- })
const unsubscribe = view.setEventHandlers({
// your event handlers
})
```
## Cập nhật đường dẫn import cho UI component \{#update-import-paths-for-ui-components\}
Adapty SDK 3.14.0 giới thiệu kiểu import đơn giản hơn cho các UI component. Thay vì import từ `react-native-adapty/dist/ui`, giờ bạn có thể import trực tiếp từ `react-native-adapty`.
Kiểu import mới nhất quán hơn với các thông lệ React Native tiêu chuẩn và giúp các câu lệnh import gọn gàng hơn. Nếu bạn đang sử dụng các UI component như `AdaptyPaywallView` hoặc `AdaptyOnboardingView`, hãy cập nhật các import như bên dưới:
```diff showLineNumbers
- import { AdaptyPaywallView } from 'react-native-adapty/dist/ui';
+ import { AdaptyPaywallView } from 'react-native-adapty';
- import { AdaptyOnboardingView } from 'react-native-adapty/dist/ui';
+ import { AdaptyOnboardingView } from 'react-native-adapty';
- import { createPaywallView } from 'react-native-adapty/dist/ui';
+ import { createPaywallView } from 'react-native-adapty';
- import { createOnboardingView } from 'react-native-adapty/dist/ui';
+ import { createOnboardingView } from 'react-native-adapty';
```
:::note
Để tương thích ngược, kiểu import cũ (`react-native-adapty/dist/ui`) vẫn được hỗ trợ. Tuy nhiên, chúng tôi khuyến nghị sử dụng kiểu import mới để đảm bảo tính nhất quán và rõ ràng.
:::
## Cập nhật event handler của onboarding trong React component \{#update-onboarding-event-handlers-in-the-react-component\}
Các event handler cho onboarding đã được chuyển ra khỏi object `eventHandlers` trong `AdaptyOnboardingView`. Nếu bạn đang hiển thị onboarding bằng `AdaptyOnboardingView`, hãy cập nhật cấu trúc xử lý sự kiện.
:::important
Lưu ý cách chúng tôi khuyến nghị triển khai các event handler. Để tránh tạo lại object mỗi lần render, hãy sử dụng `useCallback` cho các hàm xử lý sự kiện.
:::
```diff showLineNumbers
import React, { useCallback } from 'react';
- import { AdaptyOnboardingView } from 'react-native-adapty/dist/ui';
+ import { AdaptyOnboardingView } from 'react-native-adapty';
+ import type { OnboardingEventHandlers } from 'react-native-adapty';
+
+ function MyOnboarding({ onboarding }) {
+ const onAnalytics = useCallback```diff showLineNumbers - subscriptionDetails?: AdaptySubscriptionDetails; + subscription?: AdaptySubscriptionDetails; ``` 2. [AdaptySubscriptionDetails](https://react-native.adapty.io/interfaces/adaptysubscriptiondetails): - `promotionalOffer` đã bị xóa. Bây giờ ưu đãi chỉ được trả về trong thuộc tính `offer` nếu có. Trong trường hợp đó, `offer?.identifier?.type` sẽ là `'promotional'`. - `introductoryOfferEligibility` đã bị xóa (ưu đãi chỉ được trả về nếu người dùng đủ điều kiện). - `offerId` đã bị xóa. ID ưu đãi hiện được lưu trong `AdaptySubscriptionOffer.identifier`. - `offerTags` đã được chuyển sang `AdaptySubscriptionOffer.android`.
```diff showLineNumbers - introductoryOffers?: AdaptyDiscountPhase[]; + offer?: AdaptySubscriptionOffer; ios?: { - promotionalOffer?: AdaptyDiscountPhase; subscriptionGroupIdentifier?: string; }; android?: { - offerId?: string; basePlanId: string; - introductoryOfferEligibility: OfferEligibility; - offerTags?: string[]; renewalType?: 'prepaid' | 'autorenewable'; }; } ``` 3. [AdaptyDiscountPhase](https://react-native.adapty.io/interfaces/adaptydiscountphase): - Trường `identifier` đã bị xóa khỏi model `AdaptyDiscountPhase`. Identifier của ưu đãi hiện được lưu trong `AdaptySubscriptionOffer.identifier`.
```diff showLineNumbers - ios?: { - readonly identifier?: string; - }; ``` ### Xóa các model \{#remove-models\} 1. `AttributionSource`: - Chuỗi string hiện được sử dụng ở những nơi trước đây dùng `AttributionSource`. 2. `OfferEligibility`: - Model này đã bị xóa vì không còn cần thiết. Hiện tại, ưu đãi chỉ được trả về nếu người dùng đủ điều kiện. ## Xóa phương thức `getProductsIntroductoryOfferEligibility` \{#remove-getproductsintroductoryoffereligibility-method\} Trước Adapty SDK 3.3.1, các object sản phẩm luôn bao gồm ưu đãi, ngay cả khi người dùng không đủ điều kiện. Điều này yêu cầu bạn phải kiểm tra điều kiện thủ công trước khi sử dụng ưu đãi. Kể từ phiên bản 3.3.1, object sản phẩm chỉ bao gồm ưu đãi nếu người dùng đủ điều kiện. Điều này đơn giản hóa quy trình vì bạn có thể giả định người dùng đủ điều kiện nếu có ưu đãi. ## Cập nhật cách thực hiện giao dịch mua \{#update-making-purchase\} Trong các phiên bản trước, giao dịch bị hủy và đang chờ xử lý được coi là lỗi và trả về mã `2: 'paymentCancelled'` và `25: 'pendingPurchase'` tương ứng. Kể từ phiên bản 3.3.1, giao dịch bị hủy và đang chờ xử lý hiện được coi là kết quả thành công và nên được xử lý tương ứng: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` ## Cập nhật cách hiển thị paywall trong Paywall Builder \{#update-paywall-builder-paywall-presentation\} Để xem các ví dụ cập nhật, hãy tham khảo tài liệu [Hiển thị paywall mới từ Paywall Builder trong React Native](react-native-present-paywalls). ```diff showLineNumbers - import { createPaywallView } from '@adapty/react-native-ui'; + import { createPaywallView } from 'react-native-adapty/dist/ui'; const view = await createPaywallView(paywall); view.registerEventHandlers(); // handle close press, etc try { await view.present(); } catch (error) { // handle the error } ``` ## Cập nhật cách triển khai timer do developer định nghĩa \{#update-developer-defined-timer-implementation\} Đổi tên tham số `timerInfo` thành `customTimers`: ```diff showLineNumbers - let timerInfo = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } + let customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } //and then you can pass it to createPaywallView as follows: - view = await createPaywallView(paywall, { timerInfo }) + view = await createPaywallView(paywall, { customTimers }) ``` ## Sửa đổi sự kiện mua hàng trong Paywall Builder \{#modify-paywall-builder-purchase-events\} Trước đây: - Giao dịch bị hủy kích hoạt callback `onPurchaseCancelled`. - Giao dịch đang chờ xử lý trả về mã lỗi `25: 'pendingPurchase'`. Bây giờ: - Cả hai trường hợp đều được xử lý bởi callback `onPurchaseCompleted`. #### Các bước migration: 1. Xóa callback `onPurchaseCancelled`. 2. Xóa phần xử lý mã lỗi `25: 'pendingPurchase'`. 3. Cập nhật callback `onPurchaseCompleted`: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.registerEventHandlers({ // ... other optional callbacks onPurchaseCompleted(purchaseResult, product) { switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; // highlight-start case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; // highlight-end } // highlight-start return purchaseResult.type !== 'user_cancelled'; // highlight-end }, }); ``` ## Sửa đổi sự kiện custom action trong Paywall Builder \{#modify-paywall-builder-custom-action-events\} Các callback đã bị xóa: - `onAction` - `onCustomEvent` Callback mới được thêm vào: - Callback `onCustomAction(actionId)` mới. Sử dụng nó cho các custom action. ## Sửa đổi callback `onProductSelected` \{#modify-onproductselected-callback\} Trước đây, `onProductSelected` yêu cầu object `product`. Bây giờ yêu cầu `productId` dưới dạng chuỗi string. ## Xóa các tham số tích hợp bên thứ ba khỏi phương thức `updateProfile` \{#remove-third-party-integration-parameters-from-updateprofile-method\} Các identifier tích hợp bên thứ ba hiện được thiết lập bằng phương thức `setIntegrationIdentifier`. Phương thức `updateProfile` không còn chấp nhận chúng nữa. ## Cập nhật cấu hình SDK tích hợp bên thứ ba \{#update-third-party-integration-sdk-configuration\} Để đảm bảo các tích hợp hoạt động đúng với Adapty React Native SDK 3.3.1 trở lên, hãy cập nhật cấu hình SDK của bạn cho các tích hợp sau đây như được mô tả trong các phần bên dưới. Ngoài ra, nếu bạn đã sử dụng `AttributionSource` để lấy attribution identifier, hãy thay đổi code của bạn để cung cấp identifier cần thiết dưới dạng chuỗi string. ### Adjust \{#adjust\} Cập nhật code ứng dụng di động của bạn như hướng dẫn bên dưới. Để xem ví dụ code đầy đủ, hãy xem [Cấu hình SDK cho tích hợp Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers import { Adjust, AdjustConfig } from "react-native-adjust"; import { adapty } from "react-native-adapty"; var adjustConfig = new AdjustConfig(appToken, environment); // Before submiting Adjust config... adjustConfig.setAttributionCallbackListener(attribution => { // Make sure Adapty SDK is activated at this point // You may want to lock this thread awaiting of `activate` adapty.updateAttribution(attribution, "adjust"); }); // ... Adjust.create(adjustConfig); + Adjust.getAdid((adid) => { + if (adid) + adapty.setIntegrationIdentifier("adjust_device_id", adid); + }); ``` ### AirBridge \{#airbridge\} Cập nhật code ứng dụng di động của bạn như hướng dẫn bên dưới. Để xem ví dụ code đầy đủ, hãy xem [Cấu hình SDK cho tích hợp AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers import Airbridge from 'airbridge-react-native-sdk'; import { adapty } from 'react-native-adapty'; try { const deviceId = await Airbridge.state.deviceUUID(); - await adapty.updateProfile({ - airbridgeDeviceId: deviceId, - }); + await adapty.setIntegrationIdentifier("airbridge_device_id", deviceId); } catch (error) { // handle `AdaptyError` } ``` ### Amplitude \{#amplitude\} Cập nhật code ứng dụng di động của bạn như hướng dẫn bên dưới. Để xem ví dụ code đầy đủ, hãy xem [Cấu hình SDK cho tích hợp Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; try { - await adapty.updateProfile({ - amplitudeDeviceId: deviceId, - amplitudeUserId: userId, - }); + await adapty.setIntegrationIdentifier("amplitude_device_id", deviceId); + await adapty.setIntegrationIdentifier("amplitude_user_id", userId); } catch (error) { // handle `AdaptyError` } ``` ### AppMetrica \{#appmetrica\} Cập nhật code ứng dụng di động của bạn như hướng dẫn bên dưới. Để xem ví dụ code đầy đủ, hãy xem [Cấu hình SDK cho tích hợp AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import AppMetrica, { DEVICE_ID_KEY, StartupParams, StartupParamsReason } from '@appmetrica/react-native-analytics'; // ... const startupParamsCallback = async ( params?: StartupParams, reason?: StartupParamsReason ) => { const deviceId = params?.deviceId if (deviceId) { try { - await adapty.updateProfile({ - appmetricaProfileId: 'YOUR_ADAPTY_CUSTOMER_USER_ID', - appmetricaDeviceId: deviceId, - }); + await adapty.setIntegrationIdentifier("appmetrica_profile_id", 'YOUR_ADAPTY_CUSTOMER_USER_ID'); + await adapty.setIntegrationIdentifier("appmetrica_device_id", deviceId); } catch (error) { // handle `AdaptyError` } } } AppMetrica.requestStartupParams(startupParamsCallback, [DEVICE_ID_KEY]) ``` ### AppsFlyer \{#appsflyer\} Cập nhật code ứng dụng di động của bạn như hướng dẫn bên dưới. Để xem ví dụ code đầy đủ, hãy xem [Cấu hình SDK cho tích hợp AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import appsFlyer from 'react-native-appsflyer'; appsFlyer.onInstallConversionData(installData => { try { - const networkUserId = appsFlyer.getAppsFlyerUID(); - adapty.updateAttribution(installData, AttributionSource.AppsFlyer, networkUserId); + const uid = appsFlyer.getAppsFlyerUID(); + adapty.setIntegrationIdentifier("appsflyer_id", uid); + adapty.updateAttribution(installData, "appsflyer"); } catch (error) { // handle the error } }); // ... appsFlyer.initSdk(/*...*/); ``` ### Branch \{#branch\} Cập nhật code ứng dụng di động của bạn như hướng dẫn bên dưới. Để xem ví dụ code đầy đủ, hãy xem [Cấu hình SDK cho tích hợp Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import branch from 'react-native-branch'; branch.subscribe({ enComplete: ({ params, }) => { - adapty.updateAttribution(params, AttributionSource.Branch); + adapty.updateAttribution(params, "branch"); }, }); ``` ### Facebook Ads \{#facebook-ads\} Cập nhật code ứng dụng di động của bạn như hướng dẫn bên dưới. Để xem ví dụ code đầy đủ, hãy xem [Cấu hình SDK cho tích hợp Facebook Ads](facebook-ads#connect-your-app-to-facebook-ads). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import { AppEventsLogger } from 'react-native-fbsdk-next'; try { const anonymousId = await AppEventsLogger.getAnonymousID(); - await adapty.updateProfile({ - facebookAnonymousId: anonymousId, - }); + await adapty.setIntegrationIdentifier("facebook_anonymous_id", anonymousId); } catch (error) { // handle `AdaptyError` } ``` ### Firebase and Google Analytics \{#firebase-and-google-analytics\} Cập nhật code ứng dụng di động của bạn như hướng dẫn bên dưới. Để xem ví dụ code đầy đủ, hãy xem [Cấu hình SDK cho tích hợp Firebase and Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers import analytics from '@react-native-firebase/analytics'; import { adapty } from 'react-native-adapty'; try { const appInstanceId = await analytics().getAppInstanceId(); - await adapty.updateProfile({ - firebaseAppInstanceId: appInstanceId, - }); + await adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId); } catch (error) { // handle `AdaptyError` } ``` ### Mixpanel \{#mixpanel\} Cập nhật code ứng dụng di động của bạn như hướng dẫn bên dưới. Để xem ví dụ code đầy đủ, hãy xem [Cấu hình SDK cho tích hợp Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import { Mixpanel } from 'mixpanel-react-native'; // ... try { - await adapty.updateProfile({ - mixpanelUserId: mixpanelUserId, - }); + await adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelUserId); } catch (error) { // handle `AdaptyError` } ``` ### OneSignal \{#onesignal\} Cập nhật code ứng dụng di động của bạn như hướng dẫn bên dưới. Để xem ví dụ code đầy đủ, hãy xem [Cấu hình SDK cho tích hợp OneSignal](onesignal#sdk-configuration).
:::important
**Các bước tiếp theo phụ thuộc vào việc bạn đã có sản phẩm trên App Store và/hoặc Google Play chưa:**
:::
5. Nhấn **Save & Continue** và chuyển sang tab **App Store** hoặc **Google Play** để điền thông tin sản phẩm cho cửa hàng.
Bạn sẽ render paywall này trong code ứng dụng.
Trong code ứng dụng, bạn chỉ cần hardcode các placement ID. Mọi thứ khác — paywall nào chạy, sản phẩm nào nó bán, remote config — đều được cấu hình trong Adapty Dashboard và có thể thay đổi bất cứ lúc nào mà không cần cập nhật ứng dụng.
:::tip
Adapty cho phép bạn hiển thị các paywall khác nhau cho nhiều nhóm người dùng và phân tích hiệu suất. Tìm hiểu thêm về [đối tượng](audience) và [A/B test](ab-tests).
:::
## Các bước tiếp theo \{#next-steps\}
Chúc mừng bạn đã tích hợp Adapty thành công! Bây giờ bạn đã sẵn sàng phát triển doanh thu từ in-app purchase.
Chuẩn bị cho bản phát hành chính thức:
Hoặc bạn có thể tiếp tục với các nội dung sau:
- **[A/B testing](ab-tests)**: Thử nghiệm với các mức giá, thời hạn gói đăng ký, thời gian dùng thử và các yếu tố hình ảnh khác nhau để tìm ra những kết hợp hiệu quả nhất.
- **[Phân tích](how-adapty-analytics-works)**: Khám phá các chỉ số kiếm tiền chi tiết để hiểu hành vi người dùng và tối ưu hóa hiệu suất doanh thu.
- **Tích hợp**: Adapty gửi [sự kiện gói đăng ký](events) đến các công cụ phân tích và attribution của bên thứ ba, như [Amplitude](amplitude), [AppsFlyer](appsflyer), [Adjust](adjust), [Branch](branch), [Mixpanel](mixpanel), [Facebook Ads](facebook-ads), [AppMetrica](appmetrica) và [Webhook](webhook) tùy chỉnh.
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
Khả thi, nhưng cần viết thêm nhiều code và cấu hình hơn so với Chế độ Full.
| ✅ | | **Thời gian triển khai** |Cho analytics và tích hợp: Dưới một giờ
Kèm A/B test: Đến một tuần khi kiểm thử kỹ lưỡng
| Vài giờ | ## Chế độ Observer hoạt động như thế nào \{#how-observer-mode-works\} Ở Chế độ Observer, bạn báo cáo các giao dịch mới từ Apple/Google lên Adapty SDK, và Adapty SDK sẽ chuyển tiếp chúng đến backend của Adapty. Bạn có trách nhiệm quản lý quyền truy cập nội dung trả phí trong ứng dụng, hoàn tất giao dịch, xử lý gia hạn, giải quyết các vấn đề thanh toán, v.v. ## Cách thiết lập Chế độ Observer \{#how-to-set-up-observer-mode\} 1. Thiết lập tích hợp ban đầu của Adapty [với Google Play](initial-android) và [với App Store](initial_ios). 2. Bật chế độ 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 thiết lập cho [iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk), [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform#activate-adapty-sdk), và [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 3. [Báo cáo giao dịch](report-transactions-observer-mode) từ hạ tầng mua hàng hiện có của bạn lên Adapty cho iOS và các framework đa nền tảng dựa trên iOS. 4. (Tùy chọn) Nếu bạn muốn sử dụng tích hợp bên thứ ba, hãy thiết lập theo hướng dẫn trong phần [Cấu hình tích hợp bên thứ ba](configuration). :::warning Khi hoạt động ở Chế độ Observer, Adapty SDK không hoàn tất giao dịch, vì vậy hãy đảm bảo bạn tự xử lý phần này. ::: ## Cách sử dụng paywall và A/B test trong Chế độ Observer \{#how-to-use-paywalls-and-ab-tests-in-observer-mode\} Ở Chế độ Observer, Adapty SDK không thể xác định nguồn gốc của các giao dịch mua hàng vì bạn thực hiện chúng trong hạ tầng của riêng mình. Do đó, nếu bạn muốn sử dụng paywall và/hoặc A/B test trong Chế độ Observer, bạn cần liên kết giao dịch đến từ app store với paywall tương ứng trong code ứng dụng di động khi báo cáo giao dịch. Ngoài ra, các paywall được thiết kế bằng Paywall Builder cần được hiển thị theo cách đặc biệt khi sử dụng Chế độ Observer: - Hiển thị paywall trong Chế độ Observer cho [iOS](implement-observer-mode) hoặc [Android](android-present-paywall-builder-paywalls-in-observer-mode). - [Liên kết paywall với giao dịch mua hàng](report-transactions-observer-mode) khi báo cáo giao dịch trong Chế độ Observer. --- # File: migration-from-revenuecat --- --- title: "Migration từ RevenueCat" description: "Migrate từ RevenueCat sang Adapty với hướng dẫn từng bước của chúng tôi." --- Kế hoạch migration của bạn gồm 5 bước logic và mất trung bình 2 giờ. 90% tất cả các migration hoàn thành trong chưa đầy một ngày làm việc. 1. Tìm hiểu các điểm khác biệt cốt lõi; tạo và chuẩn bị tài khoản Adapty _(5 phút)_; 2. Cài đặt Adapty SDK cho nền tảng của bạn ([iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity)) thay thế RevenueCat SDK _(1 giờ)_; 3. Thiết lập [Apple App Store server notifications](enable-app-store-server-notifications) cho Adapty và (tùy chọn) [chuyển tiếp raw events](enable-app-store-server-notifications#raw-events-forwarding) _(5 phút)_; 4. Kiểm tra và phát hành bản cập nhật ứng dụng _(30 phút);_ 5. (Tùy chọn) Yêu cầu bộ phận hỗ trợ RevenueCat cung cấp dữ liệu lịch sử dưới dạng CSV _(5 phút);_ 6. (Tùy chọn) Import dữ liệu lịch sử qua bộ phận hỗ trợ Adapty _(30 phút)_. :::info Người dùng của bạn sẽ được migrate tự động Tất cả người dùng đã từng kích hoạt gói đăng ký sẽ được chuyển sang Adapty ngay khi họ mở phiên bản mới của ứng dụng có tích hợp Adapty SDK. Trạng thái xác thực gói đăng ký và quyền truy cập premium sẽ được khôi phục tự động. ::: Trước khi phát hành phiên bản mới của ứng dụng có tích hợp Adapty SDK, hãy kiểm tra [danh sách kiểm tra trước khi phát hành](release-checklist) của chúng tôi. ## Tìm hiểu các điểm khác biệt cốt lõi; tạo và chuẩn bị tài khoản Adapty \{#learn-the-core-differences-create-and-prepare-an-adapty-account\} Adapty và RevenueCat SDK được thiết kế tương tự nhau. Sự khác biệt lớn nhất nằm ở mức sử dụng mạng và tốc độ: Adapty SDK được thiết kế để cung cấp thông tin theo yêu cầu nhanh nhất có thể khi bạn cần. Ví dụ, khi yêu cầu một paywall, bạn nhận được [Remote Config](customize-paywall-with-remote-config) trước để xây dựng sẵn onboarding hoặc paywall, sau đó mới gọi lấy sản phẩm trong một request riêng. Tên gọi có một số điểm khác nhau: | RevenueCat | Adapty | | :---------- | :-------------- | | Package | Product | | Offering | Paywall | | Paywall | Paywall Builder | | Entitlement | Access level | Adapty có khái niệm [placement](placements) — một vị trí logic bên trong ứng dụng nơi người dùng có thể thực hiện mua hàng. Trong hầu hết các trường hợp, bạn có một hoặc hai placement: - Onboarding (vì 80% tất cả các giao dịch mua diễn ra ở đây); - General (hiển thị trong phần cài đặt hoặc bên trong ứng dụng sau onboarding).
## Cài đặt Adapty SDK và thay thế RevenueCat SDK \{#install-adapty-sdk-and-replace-revenuecat-sdk\}
Cài đặt Adapty SDK cho nền tảng của bạn ([iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity)) vào ứng dụng.
Bạn cần thay thế một số phương thức SDK ở phía ứng dụng. Hãy cùng xem các hàm phổ biến nhất và cách thay thế chúng bằng Adapty SDK.
### Khởi động SDK \{#sdk-activation\}
Thay `Purchases.configure` bằng `Adapty.activate`.
### Lấy paywall (offerings) \{#getting-paywalls-offerings\}
Thay `Purchases.shared.getOfferings` bằng [`Adapty.getPaywall`](fetch-paywalls-and-products#fetch-paywall-information).
Trong Adapty, bạn luôn yêu cầu paywall thông qua [placement id](placements). Trên thực tế, bạn chỉ lấy tối đa 1-2 paywall, vì vậy chúng tôi thiết kế như vậy để tăng tốc SDK và giảm mức sử dụng mạng.
### Lấy thông tin người dùng (customer profile) \{#getting-a-user-customer-profile\}
Thay `Purchases.shared.getCustomerInfo` bằng `Adapty.getProfile`.
### Lấy sản phẩm \{#getting-products\}
Trong RevenueCat, bạn dùng cấu trúc sau: `Purchases.shared.getOfferings` rồi `self.offering?.availablePackages`.
Trong Adapty, bạn trước tiên yêu cầu paywall (xem ở trên) để có ngay [Remote Config](customize-paywall-with-remote-config) của Adapty, sau đó gọi lấy sản phẩm bằng [`Adapty.getPaywallProducts`](fetch-paywalls-and-products#fetch-products).
### Thực hiện mua hàng \{#making-a-purchase\}
Thay `Purchases.shared.purchase` bằng [`Adapty.makePurchase`](making-purchases#make-purchase).
### Kiểm tra mức độ truy cập (entitlement) \{#checking-access-level-entitlement\}
Lấy hồ sơ người dùng (xem ở trên trước) rồi thay thế
`customerInfo?.entitlements["premium"]?.isActive == true`
bằng
[`profile.accessLevels["premium"]?.isActive == true`](subscription-status#retrieving-the-access-level-from-the-server).
### Khôi phục mua hàng \{#restore-purchase\}
Thay `Purchases.shared.restorePurchases` bằng [`Adapty.restorePurchases`](restore-purchase).
### Kiểm tra người dùng đã đăng nhập chưa \{#check-if-the-user-is-logged-in\}
Thay `Purchases.shared.isAnonymous` bằng `if profile.customerUserId == nil`.
### Đăng nhập người dùng \{#log-in-user\}
Thay `Purchases.shared.logIn` bằng [`Adapty.identify`](identifying-users#set-customer-user-id-after-configuration).
### Đăng xuất người dùng \{#log-out-user\}
Thay `Purchases.shared.logOut` bằng [`Adapty.logout`](identifying-users#logging-out-and-logging-in).
## Chuyển App Store server-side notifications sang Adapty \{#switch-app-store-server-side-notifications-to-adapty\}
Đọc hướng dẫn thực hiện [tại đây](migrate-to-adapty-from-another-solutions#changing-apple-server-notifications).
## Kiểm tra và phát hành phiên bản mới của ứng dụng \{#test-and-release-a-new-version-of-your-app\}
Nếu bạn đang đọc phần này, bạn đã:
- [x] Cấu hình Adapty Dashboard
- [x] Cài đặt Adapty SDK
- [x] Thay thế logic SDK bằng các hàm Adapty
- [x] Chuyển App Store server-side notifications sang Adapty và tùy chọn bật chuyển tiếp raw events sang RevenueCat
- [ ] Thực hiện mua hàng thử trong Sandbox
- [ ] Phát hành phiên bản ứng dụng mới
Nếu bạn đã hoàn thành các bước trên, hãy thực hiện mua hàng thử trong Sandbox rồi phát hành ứng dụng.
:::info
Xem qua [danh sách kiểm tra trước khi phát hành](release-checklist).
Thực hiện kiểm tra cuối cùng bằng danh sách của chúng tôi để xác nhận tích hợp hiện tại hoặc thêm các tính năng bổ sung như tích hợp [attribution](attribution-integration) hay [analytics](analytics-integration).
:::
## (Tùy chọn) Xuất dữ liệu lịch sử RevenueCat dưới dạng CSV \{#optional-export-your-revenuecat-historical-data-in-csv-format\}
:::warning
Đừng vội import dữ liệu lịch sử
Bạn nên chờ ít nhất một tuần sau khi phát hành với SDK trước khi import dữ liệu lịch sử. Trong thời gian đó, chúng tôi sẽ thu thập đầy đủ thông tin về giá giao dịch từ SDK, giúp dữ liệu bạn import chính xác hơn.
:::
Xuất dữ liệu lịch sử từ RevenueCat dưới dạng CSV theo hướng dẫn trong [tài liệu chính thức của RevenueCat](https://www.revenuecat.com/docs/integrations/scheduled-data-exports).
## (Tùy chọn) Yêu cầu bộ phận hỗ trợ RevenueCat cung cấp Google Purchase Tokens \{#optional-ask-revenuecat-support-for-google-purchase-tokens\}
Nếu bạn cần import giao dịch Google Play, hãy liên hệ bộ phận hỗ trợ RevenueCat để lấy file CSV chứa Google Purchase Tokens qua [trang hỗ trợ](https://app.revenuecat.com/settings/support) của họ. Google Purchase Token là mã định danh duy nhất do Google Play cung cấp cho mỗi giao dịch, cần thiết để theo dõi và xác minh giao dịch chính xác trong Adapty. Thông tin này không có trong file xuất tiêu chuẩn. File chứa ba cột sau:
- `user_id`
- `google_purchase_token`
- `google_product_id`
## Liên hệ chúng tôi để import dữ liệu lịch sử \{#write-us-to-import-your-historical-data\}
Liên hệ với chúng tôi qua tin nhắn trên website hoặc gửi email đến [support@adapty.io](mailto:support@adapty.io) kèm theo file CSV của bạn.
1. Gửi trực tiếp file CSV bạn đã xuất từ RevenueCat cho đội ngũ hỗ trợ của chúng tôi.
2. Nếu import giao dịch Google Play, hãy đính kèm file CSV chứa Google Purchase Tokens mà bạn nhận được từ bộ phận hỗ trợ RevenueCat.
3. Cho chúng tôi biết ID người dùng nào sẽ được dùng làm Customer User ID (định danh người dùng chính của Adapty): `rc_original_app_user_id` hay `rc_last_seen_app_user_id_alias`.
Đội ngũ hỗ trợ của chúng tôi sẽ import các giao dịch của bạn vào Adapty. Dữ liệu sau sẽ được import vào Adapty cho mỗi giao dịch:
| Tham số | Mô tả |
| ----------------------------- | ------------------------------------------------------------ |
| user_id | Customer User ID, định danh chính của người dùng trong Adapty và hệ thống của bạn. |
| apple_original_transaction_id | Đối với chuỗi gói đăng ký, đây là ngày mua của giao dịch gốc, được liên kết qua `store_original_transaction_id`. |
| google_product_id | ID sản phẩm trên Google Play Store. |
| google_purchase_token | Mã định danh duy nhất do Google Play cung cấp cho mỗi giao dịch, cần thiết để xác thực. |
| country | Quốc gia của người dùng. |
| created_at | Ngày và giờ tạo người dùng. |
| subscription_expiration_date | Ngày và giờ gói đăng ký hết hạn. |
| email | Email của người dùng cuối. |
| phone_number | Số điện thoại của người dùng cuối. |
| idfa | Identifier for Advertisers (IDFA), được Apple gán cho thiết bị của người dùng. |
| idfv | Identifier for Vendors (IDFV), mã được gán cho tất cả ứng dụng của một nhà phát triển và dùng chung trên các ứng dụng đó trong cùng một thiết bị. |
| advertising_id | Mã định danh duy nhất do hệ điều hành Android cung cấp, các nhà quảng cáo có thể dùng để theo dõi. |
| attribution_channel | Tên kênh marketing. |
| attribution_campaign | Tên chiến dịch marketing. |
| attribution_ad_group | Nhóm quảng cáo attribution. |
| attribution_ad_set | Bộ quảng cáo attribution. |
| attribution_creative | Từ khóa creative của attribution. |
Ngoài ra, các định danh tích hợp cho các tích hợp sau cũng sẽ được import: Amplitude, Mixpanel, AppsFlyer, Adjust và FacebookAds.
## Câu hỏi thường gặp \{#faq\}
### Tôi đã cài đặt thành công Adapty SDK và phát hành phiên bản ứng dụng mới. Điều gì sẽ xảy ra với những người dùng cũ chưa cập nhật lên phiên bản có Adapty SDK? \{#i-successfully-installed-adapty-sdk-and-released-a-new-app-version-with-it-what-will-happen-to-my-legacy-subscribers-who-did-not-update-to-a-version-with-adapty-sdk\}
Hầu hết người dùng sạc điện thoại qua đêm — đó là lúc App Store thường tự động cập nhật tất cả ứng dụng, vì vậy đây không phải vấn đề đáng lo ngại. Vẫn có thể có một số ít người dùng trả phí chưa cập nhật, nhưng họ vẫn có quyền truy cập vào nội dung premium. Bạn không cần lo lắng và không cần ép họ cập nhật.
### Tôi có cần xuất dữ liệu lịch sử từ RevenueCat càng sớm càng tốt không, hay tôi sẽ mất dữ liệu đó? \{#do-i-need-to-export-my-historical-data-from-revenuecat-as-quickly-as-possible-or-will-i-lose-it\}
Bạn không cần phải vội; hãy phát hành bản cập nhật với Adapty SDK trước, sau đó mới cung cấp dữ liệu lịch sử cho chúng tôi. Chúng tôi sẽ khôi phục lịch sử thanh toán của người dùng và điền vào [hồ sơ người dùng](profiles-crm) và [biểu đồ](charts).
### Tôi đang dùng MMP (AppsFlyer, Adjust, v.v.) và analytics (Mixpanel, Amplitude, v.v.). Làm sao để đảm bảo mọi thứ hoạt động bình thường? \{#i-use-mmp-appsflyer-adjust-etc-and-analytics-mixpanel-amplitude-etc-how-do-i-make-sure-that-everything-will-work\}
Trước tiên bạn cần truyền cho chúng tôi các ID của các dịch vụ bên thứ ba mà bạn muốn chúng tôi gửi dữ liệu đến, thông qua SDK của chúng tôi. Đọc hướng dẫn về [tích hợp attribution](attribution-integration) và [tích hợp analytics](analytics-integration). Đối với dữ liệu lịch sử và người dùng cũ, **hãy đảm bảo bạn truyền cho chúng tôi các ID đó từ dữ liệu bạn đã xuất từ RevenueCat.**
---
# File: migration-from-superwall
---
---
title: "Migration from Superwall"
description: "Migrate từ Superwall sang Adapty với hướng dẫn từng bước ánh xạ mọi lệnh gọi SDK và khái niệm."
---
Hầu hết các migration từ Superwall sang Adapty mất khoảng hai tiếng. Bạn thay thế SDK, trỏ thông báo server của cửa hàng về Adapty, và phát hành bản app mới. Người dùng đã mua gói đăng ký vẫn giữ quyền truy cập — Adapty khôi phục từ receipt của App Store và Google Play ngay lần khởi chạy đầu tiên.
:::info
Người dùng đăng ký sẽ được migrate tự động
Tất cả người dùng đã từng kích hoạt gói đăng ký sẽ chuyển sang Adapty ngay khi họ mở phiên bản mới của app có tích hợp Adapty SDK. Trạng thái xác thực gói đăng ký và quyền truy cập premium được khôi phục tự động.
:::
## Cách tổ chức hướng dẫn này \{#how-this-guide-is-organized\}
Migration gồm sáu bước:
1. [Ánh xạ các khái niệm Superwall sang Adapty](#map-your-superwall-concepts-to-adapty)
2. [Cài đặt Adapty SDK](#install-the-adapty-sdk)
3. [Thay thế các lệnh gọi SDK](#replace-sdk-calls)
4. [Chuyển thông báo server App Store và Google Play](#switch-app-store-and-google-play-server-notifications)
5. [Kiểm tra và phát hành](#test-and-release)
6. [(Tùy chọn) Import dữ liệu lịch sử](#optional-import-historical-data)
## Ánh xạ các khái niệm Superwall sang Adapty \{#map-your-superwall-concepts-to-adapty\}
Hầu hết các khái niệm trong Superwall đều có khái niệm tương đương trong Adapty:
| Superwall | Adapty | Thay đổi gì |
| :------------------- | :------------------------------------------------ | :--------------------------------------------------------------------------- |
| Campaign | [Placement](placements) + [Audience](audience) | Logic Campaign được tách thành placement (vị trí) và audience (quy tắc). |
| Placement | [Placement](placements) | Cùng khái niệm, cùng tên. |
| Audience filter | [Audience](audience) | Các bộ quy tắc nằm bên trong một placement. |
| Entitlement | [Access level](access-level) | Tên định danh (ví dụ: `premium`). |
| WebView paywall | [Paywall Builder paywall](adapty-paywall-builder) | Được Adapty SDK render theo native thay vì dùng `WKWebView`. |
| `PurchaseController` | Built-in | Không cần implement protocol — Adapty xử lý mua hàng. |
| Feature gating | Kiểm tra [mức độ truy cập](access-level) | Kiểm tra `profile.accessLevels["premium"]?.isActive`. |
Có hai điểm thay đổi tư duy đáng lưu ý trước khi bạn động đến code:
- **Fetch và present là hai bước riêng biệt**: `register` của Superwall gộp việc fetch paywall, đánh giá campaign, và hiển thị UI vào một lệnh gọi duy nhất. Adapty tách hai bước này — bạn fetch paywall, lấy cấu hình của nó, rồi mới present. Cách này thêm vài dòng code nhưng cho phép bạn pre-load cấu hình, hiển thị trạng thái loading tùy chỉnh, hoặc hủy việc hiển thị theo logic riêng.
- **Trạng thái gói đăng ký theo từng mức độ truy cập**: Superwall cung cấp một `subscriptionStatus` dạng published property duy nhất. Adapty trả về một [`AdaptyProfile`](https://swift.adapty.io/documentation/adapty/adaptyprofile) với các mức độ truy cập có tên, nên một người dùng có thể sở hữu cả mức độ truy cập `sports` và `science` độc lập với nhau. Để đọc đồng bộ, hãy cache profile từ `AdaptyDelegate` thay vì gọi `getProfile()` ở mỗi lần load view.
## Cài đặt Adapty SDK \{#install-the-adapty-sdk\}
Cài đặt Adapty SDK cho nền tảng của bạn — [iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity), hoặc [Capacitor](sdk-installation-capacitor) — và đồng thời gỡ SuperwallKit khỏi dự án.
## Thay thế các lệnh gọi SDK \{#replace-sdk-calls\}
Đi qua từng phần trong tích hợp của bạn và thay thế lệnh gọi Superwall bằng lệnh tương đương của Adapty. Các liên kết ở cuối mỗi mục bao gồm cả bảy nền tảng SDK — hãy chọn đúng nền tảng của app bạn.
### Khởi tạo SDK \{#initialize-the-sdk\}
Thay `Superwall.configure` bằng `Adapty.activate`.
Xem hướng dẫn cài đặt cho nền tảng của bạn — [iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity), hoặc [Capacitor](sdk-installation-capacitor).
### Xác định và đăng xuất người dùng \{#identify-and-log-out-users\}
Thay `Superwall.shared.identify` bằng `Adapty.identify` và `Superwall.shared.reset` bằng `Adapty.logout`. Cả hai SDK đều tạo hồ sơ người dùng ẩn danh ngay lần khởi chạy đầu tiên, vì vậy các lệnh gọi này chỉ cần thiết khi người dùng đăng nhập hoặc đăng xuất. Hãy fetch lại paywall sau khi xác định người dùng — người dùng mới có thể thuộc về một đối tượng khác.
Xem hướng dẫn xác định người dùng cho nền tảng của bạn — [iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Kotlin Multiplatform](kmp-identifying-users), [Unity](unity-identifying-users), hoặc [Capacitor](capacitor-identifying-users).
### Fetch và hiển thị paywall \{#fetch-and-present-a-paywall\}
Thay `Superwall.shared.register` bằng flow hai bước: fetch paywall với `Adapty.getPaywall`, load cấu hình view của nó với `AdaptyUI.getPaywallConfiguration`, rồi mới present.
Hai điểm khác biệt cần lưu ý:
- **Feature gating thay thế closure `feature:`**: Sau khi paywall đóng, kiểm tra mức độ truy cập đang hoạt động trên profile được trả về (hoặc trên `Adapty.getProfile`) rồi phân nhánh từ đó.
- **Paywall được render bởi SDK**: Superwall render paywall bên trong `WKWebView`. Adapty render Paywall Builder paywall theo native — font chữ, thông tin sản phẩm, và các nút được SDK vẽ trực tiếp.
Xem hướng dẫn nhanh về paywall cho nền tảng của bạn — [iOS](ios-quickstart-paywalls), [Android](android-quickstart-paywalls), [React Native](react-native-quickstart-paywalls), [Flutter](flutter-quickstart-paywalls), [Kotlin Multiplatform](kmp-quickstart-paywalls), [Unity](unity-quickstart-paywalls), hoặc [Capacitor](capacitor-quickstart-paywalls).
### Kiểm tra trạng thái gói đăng ký \{#check-subscription-status\}
Thay `Superwall.shared.subscriptionStatus` bằng cách kiểm tra mức độ truy cập có tên trên profile: `profile.accessLevels["premium"]?.isActive`. Theo dõi thay đổi qua `AdaptyDelegate.didLoadLatestProfile(_:)` thay vì dùng pattern `@Published` property, và cache profile ở phía bạn để đọc đồng bộ.
Xem hướng dẫn kiểm tra trạng thái gói đăng ký cho nền tảng của bạn — [iOS](ios-check-subscription-status), [Android](android-check-subscription-status), [React Native](react-native-check-subscription-status), [Flutter](flutter-check-subscription-status), [Kotlin Multiplatform](kmp-check-subscription-status), [Unity](unity-check-subscription-status), hoặc [Capacitor](capacitor-check-subscription-status).
### Xử lý mua hàng và khôi phục \{#handle-purchases-and-restores\}
Với Paywall Builder, cả hai SDK đều tự động xử lý giao dịch mua bên trong giao diện paywall — **bạn có thể bỏ qua bước này**.
Với paywall tùy chỉnh, Superwall yêu cầu implement `PurchaseController`. Adapty thì không: thay `PurchaseController.purchase` bằng `Adapty.makePurchase` và `PurchaseController.restorePurchases` bằng `Adapty.restorePurchases`. SDK tự xử lý việc xác thực.
Xem hướng dẫn nhanh về custom paywall cho nền tảng của bạn — [iOS](ios-quickstart-manual), [Android](android-quickstart-manual), [React Native](react-native-quickstart-manual), [Flutter](flutter-quickstart-manual), [Kotlin Multiplatform](kmp-quickstart-manual), [Unity](unity-quickstart-manual), hoặc [Capacitor](capacitor-quickstart-manual).
### Thiết lập thuộc tính người dùng \{#set-user-attributes\}
Thay `Superwall.shared.setUserAttributes` bằng `Adapty.updateProfile`.
Xem hướng dẫn thuộc tính người dùng cho nền tảng của bạn — [iOS](setting-user-attributes), [Android](android-setting-user-attributes), [React Native](react-native-setting-user-attributes), [Flutter](flutter-setting-user-attributes), [Kotlin Multiplatform](kmp-setting-user-attributes), [Unity](unity-setting-user-attributes), hoặc [Capacitor](capacitor-setting-user-attributes).
## Chuyển thông báo server App Store và Google Play \{#switch-app-store-and-google-play-server-notifications\}
Trỏ thông báo server của cửa hàng về Adapty. Adapty vẫn hoạt động mà không cần chúng, nhưng analytics, các tích hợp bên thứ ba, và chỉ số A/B test đều phụ thuộc vào đây:
- **App Store**: Làm theo [Bật thông báo server App Store](enable-app-store-server-notifications).
- **Google Play**: Làm theo [Bật thông báo thời gian thực cho nhà phát triển](enable-real-time-developer-notifications-rtdn).
Nếu bạn muốn chạy song song Superwall và Adapty trong quá trình triển khai, hãy dùng [raw events forwarding](enable-app-store-server-notifications#raw-events-forwarding) — Adapty sẽ proxy các sự kiện từ cửa hàng về lại Superwall trong khi bạn xác minh tích hợp mới.
## Kiểm tra và phát hành \{#test-and-release\}
Trước khi phát hành, hãy kiểm tra từng mục:
- [x] Đã cấu hình Adapty Dashboard (sản phẩm, paywall, placement, mức độ truy cập)
- [x] Đã cài đặt Adapty SDK
- [x] Đã thay thế các lệnh gọi Superwall SDK bằng các lệnh tương đương của Adapty
- [x] Đã trỏ thông báo server App Store và Google Play về Adapty
- [ ] Đã thực hiện giao dịch mua trong sandbox
- [ ] Đã nộp bản phát hành app mới
Xem qua [danh sách kiểm tra trước khi phát hành](release-checklist) để xác thực lần cuối.
## (Tùy chọn) Import dữ liệu lịch sử \{#optional-import-historical-data\}
Superwall không sở hữu trạng thái gói đăng ký của bạn — App Store và Google Play mới là nơi lưu trữ. Adapty xác thực receipt ngay lần khởi chạy đầu tiên, nên người dùng đã mua vẫn giữ quyền truy cập mà không cần import gì.
Nếu bạn muốn backfill các giao dịch lịch sử vào Adapty analytics, hãy làm theo [Import dữ liệu lịch sử vào Adapty](importing-historical-data-to-adapty). Đợi ít nhất một tuần sau khi phát hành SDK để SDK có thời gian thu thập giá mua hàng mới.
## Câu hỏi thường gặp \{#faq\}
### Điều gì xảy ra với người dùng đã mua gói đăng ký nhưng chưa cập nhật app? \{#what-happens-to-subscribers-who-dont-update-the-app\}
Hầu hết người dùng tự động cập nhật app qua đêm, nên tỷ lệ người dùng còn dùng phiên bản cũ giảm nhanh. Người dùng đăng ký trên phiên bản cũ vẫn giữ quyền truy cập trực tiếp qua App Store hoặc Google Play — bạn không cần ép buộc cập nhật.
### Audience của Superwall campaign có chuyển sang được không? \{#do-my-superwall-campaign-audiences-carry-over\}
Không. Bộ lọc audience của Superwall và đối tượng trong Adapty được cấu hình ở hai dashboard khác nhau và dùng các định danh khác nhau. Hãy tạo lại targeting của bạn dưới dạng [đối tượng](audience) bên trong [placement](placements) Adapty. Hầu hết các app chỉ có một hoặc hai placement (onboarding và một trigger trong app), nên việc tái tạo thường khá nhanh.
### Adapty có tương đương với `getPresentationResult` không? \{#does-adapty-have-an-equivalent-to-getpresentationresult\}
Không có lệnh gọi đơn lẻ nào tương đương. Để kiểm tra xem một placement có hiển thị paywall không, hãy gọi `Adapty.getPaywall(placementId:)` và phân nhánh dựa trên kết quả. Nếu lệnh gọi thành công, một paywall đã được gán cho đối tượng của người dùng đó. Nếu thất bại vì không có paywall nào được cấu hình, hãy bỏ qua việc hiển thị và chạy logic dự phòng của bạn.
---
# File: importing-historical-data-to-adapty
---
---
title: "Nhập dữ liệu lịch sử vào Adapty"
description: "Nhập dữ liệu lịch sử vào Adapty để có phân tích chi tiết."
---
Sau khi cài đặt SDK Adapty và phát hành ứng dụng, bạn có thể xem người dùng và người đăng ký trong phần [Profiles](profiles-crm). Nhưng nếu bạn có hạ tầng cũ và cần migrate sang Adapty, hoặc chỉ đơn giản muốn xem dữ liệu hiện có trong Adapty thì sao?
:::note
Nhập dữ liệu không bắt buộc
Adapty sẽ tự động cấp mức độ truy cập cho người dùng cũ và khôi phục các sự kiện mua hàng của họ khi họ mở ứng dụng đã tích hợp SDK Adapty. Với trường hợp này, việc nhập dữ liệu lịch sử là không cần thiết. Tuy nhiên, nhập dữ liệu sẽ đảm bảo phân tích chính xác nếu bạn có số lượng lớn giao dịch lịch sử, dù nhìn chung thì không bắt buộc cho migration.
:::
Để nhập dữ liệu vào Adapty:
1. Xuất các giao dịch của bạn ra file CSV (cần cung cấp file riêng cho iOS, Android và Stripe). Vui lòng tham khảo [phần định dạng file nhập](importing-historical-data-to-adapty#import-file-format) bên dưới để biết yêu cầu chi tiết.
2. Nếu file nào vượt quá 1 GB, hãy chuẩn bị một mẫu dữ liệu khoảng 100 dòng.
3. Tải tất cả các file lên Google Drive (bạn có thể nén lại, nhưng giữ chúng tách biệt).
4. Đối với giao dịch iOS, hãy đảm bảo phần **In-app purchase API** trong [**App settings**](https://app.adapty.io/settings/ios-sdk) đã được điền đầy đủ **Issuer ID**, **Key ID** và **Private key** (file .P8) dù bạn sử dụng StoreKit 1. Xem hướng dẫn chi tiết tại phần [Provide Issuer ID and Key ID](app-store-connection-configuration#step-2-provide-issuer-id-and-key-id) và [Upload In-App Purchase Key file](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file).
5. Chia sẻ các đường dẫn với đội ngũ của chúng tôi qua [email](mailto:support@adapty.io) hoặc qua chat trực tuyến trong Adapty Dashboard.
Đừng lo, việc nhập dữ liệu lịch sử sẽ không tạo ra bản trùng lặp, ngay cả khi dữ liệu đó trùng với các mục đã có trong Adapty.
## Giới hạn đã biết đối với Android \{#known-limitations-for-android\}
1. Chỉ các gói đăng ký đang hoạt động mới được khôi phục; các giao dịch đã hết hạn sẽ không được khôi phục.
2. Chỉ lần gia hạn gần nhất trong một gói đăng ký mới được khôi phục; toàn bộ chuỗi mua hàng sẽ không được khôi phục.
3. Nếu giá sản phẩm đã thay đổi kể từ lần mua, giá hiện tại sẽ được sử dụng, điều này có thể dẫn đến giá không chính xác.
:::note
Nếu bạn có lượng lớn giao dịch Android, bạn có thể cần [yêu cầu tăng hạn mức Google Play Developer API](google-play-quota-increase) trước khi bắt đầu nhập để tránh vượt quá giới hạn API mặc định.
:::
## Định dạng file nhập \{#import-file-format\}
:::tip
Nếu bạn đang migrate từ RevenueCat, bạn có thể gửi thẳng file xuất từ RevenueCat — không cần chuyển đổi. Xem [tài liệu của RevenueCat](https://www.revenuecat.com/docs/integrations/scheduled-data-exports) để biết hướng dẫn xuất file.
:::
Hãy chuẩn bị dữ liệu trong một hoặc nhiều file đáp ứng các yêu cầu sau:
- [ ] Định dạng file là .CSV.
- [ ] File riêng cho Android, iOS và Stripe.
- [ ] Mỗi file nhập chứa tất cả [các cột bắt buộc](importing-historical-data-to-adapty#required-fields).
- [ ] Các cột trong file nhập có tiêu đề.
- [ ] Tiêu đề cột phải khớp chính xác với cột **Column name** trong bảng bên dưới. Vui lòng kiểm tra lỗi chính tả.
- [ ] Các cột không bắt buộc có thể vắng mặt trong file. Đừng thêm cột trống cho dữ liệu bạn không có.
- [ ] File nhập không được có cột thừa không được đề cập trong bảng. Nếu có, hãy xóa chúng đi.
- [ ] Các giá trị được phân tách bằng dấu phẩy.
- [ ] Các giá trị không được đặt trong dấu ngoặc kép.
- [ ] Nếu có nhiều **apple_original_transaction_id** cho một người dùng, hãy thêm tất cả chúng thành các dòng riêng biệt cho mỗi **apple_original_transaction_id**. Nếu không, chúng tôi có thể không khôi phục được các giao dịch mua consumable.
Vui lòng sử dụng các file sau làm mẫu cho [iOS](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/adapty_import_ios_sample.csv) và [Android](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/adapty_import_android_sample.csv).
### Các cột có thể dùng trong file nhập \{#available-import-file-columns\}
| Column name | Presence | Mô tả |
|-----------|--------|-----------|
| **user_id** | bắt buộc | ID người dùng của bạn |
| **apple_original_transaction_id** | bắt buộc cho iOS | ID giao dịch gốc hoặc OTID ([tìm hiểu thêm](https://developer.apple.com/documentation/appstoreserverapi/originaltransactionid)), được dùng trong cơ chế nhập StoreKit 2. Vì một người dùng có thể có nhiều OTID, chỉ cần cung cấp ít nhất một OTID để nhập thành công.
**Lưu ý:** Chúng tôi yêu cầu thông tin xác thực In-app purchase API cho việc nhập này phải được thiết lập trong Adapty Dashboard của bạn. Tìm hiểu cách thực hiện [tại đây](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file).
| | **google_product_id** | bắt buộc cho Google | ID sản phẩm trong Google Play Store. | | **google_purchase_token** | bắt buộc cho Google | Mã định danh duy nhất đại diện cho người dùng và ID sản phẩm của in-app purchase mà họ đã mua | | **google_is_subscription** | bắt buộc cho Google | Các giá trị có thể là `1` \| `0` | | **stripe_token** | bắt buộc cho Stripe | Token của đối tượng Stripe đại diện cho một giao dịch mua duy nhất. Có thể là token của Stripe Subscription (`sub_...`) hoặc Payment Intent (`pi_...`). | | **subscription_expiration_date** | tùy chọn | Ngày hết hạn gói đăng ký, tức là ngày tính phí tiếp theo, ngày và giờ có múi giờ (2020-12-31T23:59:59-06:00) | | **created_at** | tùy chọn | Ngày và giờ tạo hồ sơ người dùng (2019-12-31 23:59:59-06:00) | | **birthday** | tùy chọn | Ngày sinh của người dùng theo định dạng 2000-12-31 | | **email** | tùy chọn | Email của người dùng | | **gender** | tùy chọn | Giới tính của người dùng | | **phone_number** | tùy chọn | Số điện thoại của người dùng | | **country** | tùy chọn | định dạng [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) | | **first_name** | tùy chọn | Tên của người dùng | | **last_name** | tùy chọn | Họ của người dùng | | **last_seen** | tùy chọn | Ngày và giờ có múi giờ (2020-12-31T23:59:59-06:00) | | **idfa** | tùy chọn | Mã định danh cho nhà quảng cáo (IDFA) là mã định danh thiết bị ngẫu nhiên do Apple gán cho thiết bị của người dùng. Chỉ áp dụng cho ứng dụng iOS | | **idfv** | tùy chọn | Mã định danh cho nhà cung cấp (IDFV) là mã duy nhất được gán cho tất cả ứng dụng do một nhà phát triển tạo ra, trong trường hợp này là ứng dụng của bạn. Chỉ áp dụng cho ứng dụng iOS | | **advertising_id** | tùy chọn | Advertising ID là mã duy nhất do Hệ điều hành Android gán mà nhà quảng cáo có thể dùng để định danh thiết bị của người dùng | | **amplitude_user_id** | tùy chọn | ID người dùng từ Amplitude | | **amplitude_device_id** | tùy chọn | ID thiết bị từ Amplitude | | **mixpanel_user_id** | tùy chọn | ID người dùng từ Mixpanel | | **appmetrica_profile_id** | tùy chọn | ID hồ sơ người dùng từ AppMetrica | | **appmetrica_device_id** | tùy chọn | ID thiết bị từ AppMetrica | | **appsflyer_id** | tùy chọn | Mã định danh duy nhất từ AppsFlyer | | **adjust_device_id** | tùy chọn | ID thiết bị từ Adjust | | **facebook_anonymous_id** | tùy chọn | Mã định danh duy nhất do Facebook tạo ra cho người dùng tương tác với ứng dụng hoặc trang web của bạn theo cách ẩn danh, tức là họ không đăng nhập vào Facebook | | **branch_id** | tùy chọn | Mã định danh duy nhất từ Branch | | **attribution_source** | tùy chọn | Nguồn tích hợp attribution, ví dụ: appsflyer | | **attribution_status** | tùy chọn | organic | | **attribution_channel** | tùy chọn | Kênh attribution đã mang lại giao dịch | | **attribution_campaign** | tùy chọn | Chiến dịch attribution đã mang lại giao dịch | | **attribution_ad_group** | tùy chọn | Nhóm quảng cáo attribution đã mang lại giao dịch | | **attribution_ad_set** | tùy chọn | Bộ quảng cáo attribution đã mang lại giao dịch | | **attribution_creative** | tùy chọn | Các yếu tố hình ảnh hoặc văn bản cụ thể được sử dụng trong quảng cáo hoặc chiến dịch marketing, được theo dõi để xác định hiệu quả trong việc thúc đẩy các hành động mong muốn như click, chuyển đổi hoặc cài đặt | | **custom_attributes** | tùy chọn | Định nghĩa tối đa 30 thuộc tính tùy chỉnh dưới dạng từ điển JSON theo định dạng key-value:Định dạng: `"{'string_value': 'some_value', 'float_value': 123.0, 'int_value': 456}"`.
Lưu ý cách dùng dấu nháy đơn và nháy kép trong định dạng. Lưu ý rằng các giá trị boolean và integer sẽ được chuyển thành float.
| ### Các trường bắt buộc \{#required-fields\} Có 2 nhóm trường bắt buộc cho mỗi nền tảng: **user_id** và dữ liệu xác định giao dịch mua hàng cụ thể của nền tảng tương ứng. Tham khảo bảng bên dưới để biết các trường bắt buộc theo từng nền tảng. | Nền tảng | Các trường bắt buộc | |--------|---------------| | iOS |user_id
apple_original_transaction_id
| | Android |user_id
google_product_id
google_purchase_token
google_is_subscription
| | Stripe |user_id
stripe_token
| Nếu thiếu các trường này, Adapty sẽ không thể lấy được giao dịch. Để có phân tích cohort chính xác, hãy chỉ định `created_at`. Nếu không cung cấp, chúng tôi sẽ lấy ngày cài đặt bằng với ngày mua hàng đầu tiên. ### Nhập dữ liệu vào Adapty \{#import-data-to-adapty\} Vui lòng liên hệ với chúng tôi và chia sẻ file nhập qua [support@adapty.io](mailto:support@adapty.io) hoặc qua chat trực tuyến trong [Adapty Dashboard](https://app.adapty.io/overview). --- # File: migrate-integrations-to-adapty --- --- title: "Migrate tích hợp sang Adapty" description: "Chuyển đổi các tích hợp analytics và attribution từ giải pháp cũ sang Adapty mà không bị trùng lặp sự kiện hay gián đoạn chiến dịch." --- Migrate sang Adapty không chỉ đơn giản là đổi SDK. Các tích hợp analytics và attribution của bên thứ ba — như Amplitude và Adjust — cũng cần được chuyển đổi đồng bộ. Nếu thực hiện cẩn thận, quá trình chuyển đổi sẽ có rất ít sự kiện bị trùng hoặc bị thiếu và không làm gián đoạn chiến dịch của bạn. ## Ánh xạ sự kiện \{#map-your-events\} Tên sự kiện có thể tùy chỉnh trong hầu hết các tích hợp Adapty. Bạn có thể cấu hình chúng để khớp với tên đã dùng trong dashboard và chiến dịch hiện tại. Báo cáo analytics và chiến dịch của bạn sẽ tiếp tục hoạt động với cùng tên sự kiện sau khi chuyển đổi. Để xem danh sách đầy đủ các sự kiện trong Adapty, hãy xem [Sự kiện](events). Với Adjust, tích hợp sử dụng event ID thay vì tên sự kiện tùy chỉnh. Hãy chuyển các event ID hiện có từ Adjust dashboard sang cấu hình tích hợp Adapty. Xem [hướng dẫn tích hợp Adjust](adjust) để biết thêm chi tiết. ## Cách Adapty tạo sự kiện tích hợp \{#how-adapty-creates-integration-events\} Để gửi sự kiện đến một tích hợp, Adapty cần có hồ sơ người dùng. Hồ sơ được tạo theo một trong hai cách: - **Import lịch sử**: Hồ sơ được tạo khi bạn [import dữ liệu giao dịch lịch sử](importing-historical-data-to-adapty) trước khi SDK hoạt động. - **Tương tác qua SDK**: Hồ sơ được tạo tự động khi người dùng mở ứng dụng có tích hợp Adapty SDK lần đầu tiên. Adapty nhận biết các giao dịch mua được thực hiện trong hệ thống cũ theo thời gian thực. Nhưng nó chỉ có thể gửi sự kiện tích hợp khi hồ sơ của người mua đã tồn tại. Hồ sơ được tạo khi người dùng mở ứng dụng có Adapty SDK. Những người dùng không cập nhật lên phiên bản mới sẽ không tạo ra sự kiện tích hợp. ## Chuẩn bị trước ngày migration \{#prepare-before-migration-day\} ### Loại trừ sự kiện lịch sử \{#exclude-historical-events\} Bật **Exclude Historical Events** trong [cài đặt tích hợp](configuration) của bạn. Điều này ngăn các sự kiện xảy ra trước phiên Adapty SDK đầu tiên của người dùng không bị gửi đến tích hợp. Cài đặt này đặc biệt quan trọng trong quá trình [import lịch sử](importing-historical-data-to-adapty), khi Adapty xử lý một lượng lớn giao dịch quá khứ cùng một lúc. Nếu không có nó, các giao dịch đó sẽ tạo ra một lượng lớn sự kiện trong công cụ analytics của bạn. ### Thiết lập tích hợp trước \{#set-up-the-integration-in-advance\} Adapty cho phép bạn cấu hình và kiểm tra tích hợp trong khi vẫn giữ nó ở trạng thái tắt. Bạn có thể thiết lập thông tin xác thực, ánh xạ sự kiện và bộ lọc mà không cần kích hoạt tích hợp cho đến khi sẵn sàng. Cài đặt được lưu lại khi bạn bật, nên không mất gì khi giữ nó tắt đến ngày migration. Để tìm tích hợp của bạn, xem [Tích hợp attribution](attribution-integration), [Tích hợp analytics](analytics-integration), [Tích hợp dịch vụ nhắn tin](messaging), hoặc [Tích hợp Webhook và ETL](webhook-and-etl). ## Chuyển đổi vào ngày migration \{#switch-on-migration-day\} Tắt tích hợp trong giải pháp cũ và bật nó trong Adapty cùng một lúc. Chạy song song cả hai sẽ tạo ra sự kiện trùng lặp. Tạm dừng các chiến dịch mua lớn vào ngày migration. Điều này giảm thiểu rủi ro lỗi tối ưu hóa chiến dịch do các sự kiện trong khoảng thời gian chuyển giao. ## Điều cần lưu ý \{#what-to-expect\} Một số sự kiện tích hợp bị thiếu hoặc trùng lặp trong quá trình migration là không thể tránh khỏi. Khi việc chuyển đổi được thực hiện đúng cách, số lượng sự kiện bị ảnh hưởng là không đáng kể. Nguyên nhân chính gây ra khoảng trống là thời điểm đã đề cập ở trên: Adapty chỉ có thể gửi sự kiện tích hợp cho một giao dịch mua sau khi hồ sơ người dùng tồn tại. Các giao dịch thực hiện trong hệ thống cũ sẽ không tạo ra sự kiện tích hợp Adapty cho đến khi người mua mở ứng dụng có Adapty SDK. ## Tích hợp vs. thông báo server-to-server \{#integrations-vs-server-to-server-notifications\} Adapty khuyến nghị sử dụng tích hợp thay vì chuyển tiếp thông báo server-to-server thô từ cửa hàng trực tiếp đến công cụ analytics hoặc attribution của bạn. Với tích hợp: - **Định dạng thống nhất**: Sự kiện từ tất cả các cửa hàng — App Store, Google Play, Stripe — sử dụng cùng một định dạng sự kiện. - **Dữ liệu phong phú hơn**: Sự kiện bao gồm dữ liệu mà Adapty thu thập, chẳng hạn như trạng thái gói đăng ký và thuộc tính người dùng. Thông báo thô không bao gồm điều này. --- # File: whats-new --- --- title: "Có gì mới" description: "Cập nhật các tính năng và cải tiến mới nhất trong Adapty" --- Khám phá các tính năng mới nhất, cải tiến, cập nhật SDK và nâng cấp tài liệu giúp bạn tối ưu hóa chiến lược kiếm tiền từ ứng dụng. Trang này tổng hợp những bản phát hành quan trọng nhất mỗi tháng. :::note Có phản hồi về các tính năng mới? Chúng tôi rất muốn nghe ý kiến từ bạn! Liên hệ với chúng tôi qua [Bảng phản hồi sản phẩm](https://adapty.featurebase.app/en?b=69831ba5e82e7a3391632ec2). ::: ## Tháng 6 năm 2026 \{#june-2026\} - **A/B Test CPP trong Apple Ads Manager**: So sánh các trang sản phẩm tùy chỉnh với nhau ngay trong Apple Ads. Chọn từ 2 đến 4 trang — bao gồm cả trang mặc định hiện tại — và Apple Ads sẽ phân phối traffic luân phiên giữa các trang đó rồi báo cáo trang nào có tỷ lệ chuyển đổi tốt nhất. [Tìm hiểu thêm](ads-manager-cpp-ab-tests) - **Adapty Mail API**: Gửi hồ sơ người dùng và giao dịch đến Adapty Mail trực tiếp từ server của bạn, không cần định tuyến dữ liệu qua Adapty SDK. Dùng để khởi tạo danh sách người đăng ký, tái sử dụng người đăng ký từ các ứng dụng khác, hoặc giữ backend làm nguồn dữ liệu chính. [Tìm hiểu thêm](mail-send-data-via-api) - **Hiển thị paywall nhắm mục tiêu Apple Ads ngay lần khởi chạy đầu tiên**: Attribution của Apple Ads đến sau khi SDK kích hoạt, nên nếu yêu cầu paywall quá sớm sẽ bỏ lỡ đối tượng Apple Ads của bạn. Dùng `AdaptyProfile.appliedAttributionSources` để hiển thị paywall nhắm mục tiêu Apple Ads ngay khi dữ liệu attribution về. [iOS](ios-show-aa-targeted-paywall) | [React Native](react-native-show-aa-targeted-paywall) | [Capacitor](capacitor-show-aa-targeted-paywall) - **Tự động lưu trong Flow Builder**: Flow Builder giờ tự động lưu tiến trình của bạn mỗi phút một lần, nên bạn sẽ không mất công chỉnh sửa chưa lưu khi rời khỏi trang. Bạn vẫn có thể lưu nháp thủ công bằng **Cmd/Ctrl + S**. [Tìm hiểu thêm](builder-save-publish) - **Video hướng dẫn Flow Builder mới**: Hai video hướng dẫn mới bao gồm cách xây dựng điều hướng giữa các màn hình flow và thiết kế trạng thái phần tử như đã chọn, đang hoạt động và bị vô hiệu hóa. [Điều hướng trong flow](onboarding-navigation-branching) | [Trạng thái phần tử](builder-element-states) - **Tài liệu bằng tiếng Nhật và tiếng Việt**: Tài liệu Adapty hiện đã có bằng tiếng Nhật (日本語) và tiếng Việt (Tiếng Việt). Chuyển đổi ngôn ngữ bằng bộ chọn ngôn ngữ ở thanh điều hướng phía trên. ## Tháng 5 năm 2026 \{#may-2026\} - **Flows (Beta)**: Tạo toàn bộ chuỗi màn hình trong trình tạo no-code trực quan — paywall một màn hình, onboarding nhiều bước, và mọi thứ ở giữa, tất cả trong một flow. Các màn hình hiển thị theo cách native mà không cần web view, và bạn có thể cập nhật nội dung, thiết kế cũng như logic mà không cần phát hành bản cập nhật ứng dụng. Hiện hỗ trợ iOS SDK v4 trở lên. [Tìm hiểu thêm](adapty-flow-builder) - **Autopilot giờ đây thích nghi với kết quả test của bạn**: Hoạt động như một AI growth manager, Autopilot cập nhật kế hoạch tăng trưởng sau mỗi vòng hoàn thành. Giả thuyết tiếp theo được xây dựng dựa trên những thí nghiệm bạn đã chạy, những thí nghiệm nào đã thắng, và những hướng nào vẫn còn đáng khám phá — thay vì tuân theo một trình tự cố định. [Tìm hiểu thêm](autopilot-how-it-works#how-autopilot-decides-what-to-recommend) - **Activation ARPU trong Autopilot Market Insights**: Một biểu đồ mới so sánh doanh thu trung bình trên mỗi lượt cài đặt mới của ứng dụng với mức trung bình của danh mục. Kết hợp với phễu chuyển đổi — tỷ lệ chuyển đổi cao kết hợp với Activation ARPU thấp có thể cho thấy sản phẩm đang được định giá quá thấp. [Tìm hiểu thêm](autopilot-analysis#activation-arpu) - **Analytics trong Adapty Mail**: So sánh chỉ số phân phối và doanh thu được ghi nhận từ email cho từng chiến dịch trong một giao diện. Nhóm, phân tích chi tiết và lọc theo chiến dịch, phân khúc, biến thể A/B test, nội dung tin nhắn hoặc trigger, rồi xem chi tiết từng hàng. [Tìm hiểu thêm](mail-analytics) - **Hồ sơ thương hiệu trong Adapty Mail**: Hồ sơ tổng hợp giúp định hướng nội dung email, giọng điệu, hình ảnh và nội dung paywall trên web. Adapty xây dựng hồ sơ này từ trang danh sách ứng dụng trên cửa hàng, trang đích, trang pháp lý và các hồ sơ mạng xã hội của bạn — bạn có thể xem lại hoặc chỉnh sửa từng phần ngay trực tiếp. [Tìm hiểu thêm](mail-brand) - **Dự đoán trong Adapty UA**: Dự đoán doanh thu, ROAS, lợi nhuận quảng cáo, ARPU và ARPPU cho từng cohort, giúp bạn so sánh các chiến dịch trước khi chúng đủ thời gian để đánh giá. Dự đoán được xây dựng từ dữ liệu cohort lịch sử của chính ứng dụng, cập nhật hàng ngày, và có sẵn cho các khoảng thời gian cohort từ D0 đến D360 hoặc một ngày tùy chỉnh. [Tìm hiểu thêm](ua-predicted-metrics) - **Các trường mới trong Adapty UA custom S3 export**: Custom S3 export hiện bao gồm `bundle_id`, `device_brand`, `device_model`, `os_version`, `app_version` và `sdk_version`. Phân tách và kết hợp dữ liệu attribution theo thiết bị và phiên bản ứng dụng ở phía downstream. [Tìm hiểu thêm](ua-custom-s3) - **Đối tượng placement trong CLI**: Các lệnh `adapty placements create` và `adapty placements update` hiện hỗ trợ flag `--audiences` — một mảng JSON gồm các mục `{segment_ids, paywall_id, priority}` — cho phép bạn nhắm paywall khác nhau đến các phân khúc khác nhau ngay từ terminal. Lệnh `adapty paywalls placements` mới liệt kê tất cả các placement đang sử dụng một paywall nhất định, giúp bạn xem trước tác động trước khi thay đổi. [Tìm hiểu thêm](developer-cli-reference#placements) - **Tài liệu bằng tiếng Tây Ban Nha**: Tài liệu Adapty hiện đã có phiên bản tiếng Tây Ban Nha (Español). Chuyển ngôn ngữ bằng bộ chọn ngôn ngữ ở thanh điều hướng phía trên. ## Tháng 4 năm 2026 \{#april-2026\} - **Adapty Mail**: Chiến dịch email được tạo bởi AI, giúp chuyển đổi người dùng dùng thử thành người đăng ký trả phí. Xây dựng, gửi và attribution chiến dịch ngay trong dự án Adapty của bạn — không cần nền tảng email riêng. [Tìm hiểu thêm](adapty-mail) - **Chẩn đoán Paywall trong Autopilot**: Biết cần sửa gì trên paywall trước khi tạo test. Tải lên ảnh chụp màn hình và Autopilot sẽ trả về các đề xuất dựa trên benchmark từ các ứng dụng hoạt động tốt nhất trong danh mục của bạn, cùng với gợi ý bố cục và nội dung do AI tạo ra. Các đề xuất có benchmark sẽ trở thành các vòng A/B test trong kế hoạch tăng trưởng của bạn. [Tìm hiểu thêm](autopilot-analysis#paywall-analysis) - **Hướng dẫn rõ ràng hơn cho từng gợi ý của Autopilot**: Mỗi giả thuyết giờ đây đều giải thích rõ lý do tại sao nó quan trọng (giải thích dựa trên dữ liệu về cách paywall của bạn lệch khỏi các mẫu đã được xác lập), cần thay đổi gì và cách thiết lập A/B test, cũng như các chỉ số cần theo dõi trong phần mới "Cách diễn giải kết quả của bạn". [Tìm hiểu thêm](autopilot-execute-plan#step-1-view-the-hypothesis) - **Giữ cho kế hoạch tăng trưởng Autopilot luôn cập nhật**: Làm mới phân tích để lấy dữ liệu thị trường mới nhất và các đề xuất mới, đồng thời xem lại các đề xuất trước đó trong lịch sử phiên bản nếu các đề xuất mới chưa phù hợp. Các giả thuyết được nhóm theo các tab Top priority, All, Pricing, Visual, Geo-pricing và Archived. [Tìm hiểu thêm](autopilot-growth-plan) - **Phân bổ doanh thu theo thời hạn trong Autopilot**: Xem liệu doanh thu của bạn có đang tập trung quá mức vào một thời hạn gói đăng ký hay không. Một biểu đồ Market Insights mới hiển thị tỷ lệ doanh thu theo thời hạn của bạn cùng với mức trung bình ngành cho danh mục và quốc gia của bạn. [Tìm hiểu thêm](autopilot-analysis#revenue-distribution-by-duration) - **Cập nhật dự đoán LTV và doanh thu**: Dự đoán LTV và doanh thu giờ đây sử dụng dữ liệu giữ chân cohort của chính ứng dụng bạn khi có đủ lịch sử, và dùng mức trung bình trên nhiều ứng dụng trong trường hợp còn lại — vì vậy ngay cả các ứng dụng mới cũng có được dự đoán khả dụng trong analytics và A/B test. [Tìm hiểu thêm](predicted-ltv-and-revenue) - **Gửi tất cả sự kiện trong Adapty UA**: Cung cấp cho Meta và TikTok bức tranh đầy đủ hơn về các lượt chuyển đổi để tối ưu hóa mô hình đối tượng. Adapty hiện hỗ trợ chuyển tiếp lượt cài đặt và giao dịch từ người dùng organic và không có attribution đến pixel của bạn, không chỉ những người dùng được khớp với một chiến dịch. [Meta](ua-facebook#send-all-events) | [TikTok](ua-tiktok#send-all-events) - **Tài liệu bằng tiếng Nga và tiếng Thổ Nhĩ Kỳ**: Tài liệu Adapty hiện đã có sẵn bằng tiếng Nga (Русский) và tiếng Thổ Nhĩ Kỳ (Türkçe). Chuyển đổi ngôn ngữ bằng bộ chọn ngôn ngữ trên thanh điều hướng phía trên. ## Tháng 3 năm 2026 \{#march-2026\} - **Developer CLI**: Quản lý tài khoản Adapty của bạn ngay từ terminal mà không cần mở Dashboard. CLI cho phép bạn tạo ứng dụng, định nghĩa mức độ truy cập, thiết lập sản phẩm, tạo paywall và cấu hình placement — tất cả đều có thể viết script cho các môi trường tự động hóa. Ngoài ra còn có [Adapty CLI skill](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) để hỗ trợ các công cụ AI coding làm việc với CLI. [Tìm hiểu thêm](developer-cli) - **Trang Overview trong Apple Ads Manager**: Xem tất cả các chỉ số quan trọng của Apple Ads tại một nơi, mỗi chỉ số kèm theo biểu đồ xu hướng. Lọc theo ứng dụng bằng dropdown ở header, tùy chỉnh các chỉ số hiển thị, và điều chỉnh loại biểu đồ cũng như cách hiển thị doanh thu. [Tìm hiểu thêm](ads-manager-overview) - **Market Intelligence trong Apple Ads Manager**: Xem các từ khóa mà đối thủ của bạn đang chạy quảng cáo trên hơn 50 quốc gia, và thêm trực tiếp các từ khóa hiệu quả nhất vào chiến dịch của bạn. [Tìm hiểu thêm](ads-manager-market-intelligence) - **Tự động hóa keyword toàn chu trình trong Apple Ads Manager**: Tự động điều chỉnh giá thầu, tạm dừng hoặc kích hoạt keyword, và di chuyển chúng giữa các nhóm quảng cáo dựa trên các quy tắc hiệu suất bạn định nghĩa. [Tìm hiểu thêm](ads-manager-automations-keyword-rules) - **Lịch sử giá thầu trong Apple Ads Manager**: Xem toàn bộ lịch sử thay đổi cho giá thầu CPT của bất kỳ keyword nào — thời điểm mỗi thay đổi xảy ra, giá trị trước và sau, và quy tắc tự động hóa nào đã kích hoạt nó. [Tìm hiểu thêm](ads-manager-manage-keywords#bid-history) - **Các vòng thiết kế trong Autopilot**: Các gợi ý thiết kế paywall giờ đây là các vòng chính thức trong kế hoạch tăng trưởng của bạn — được liệt kê trong thanh bên cùng với các vòng monetization. Mỗi vòng thiết kế bao gồm bản phác thảo thiết kế, mô tả về thời điểm phù hợp để áp dụng mẫu đó, và các chỉ số quan trọng mà nó hướng đến. [Tìm hiểu thêm](autopilot-growth-plan#view-the-growth-plan) - **Thêm giả thuyết của riêng bạn vào Autopilot**: Mở rộng kế hoạch tăng trưởng với các vòng tùy chỉnh. Thêm tiêu đề, mô tả, loại vòng (kiếm tiền hoặc giao diện), chỉ số mục tiêu và — đối với các vòng kiếm tiền — các sản phẩm liên quan. [Tìm hiểu thêm](autopilot-growth-plan#add-your-own-hypothesis) - **Sắp xếp lại các vòng Autopilot**: Kéo và sắp xếp lại các giai đoạn trong kế hoạch tăng trưởng để chạy các thử nghiệm theo thứ tự phù hợp nhất với chiến lược của bạn. [Tìm hiểu thêm](autopilot) - **Định giá theo địa lý trong Autopilot**: Thử nghiệm thay đổi giá theo từng quốc gia như một loại round mới trong kế hoạch tăng trưởng của bạn. Dựa trên dữ liệu Market Insights, Autopilot đề xuất nên tăng, giảm hay giữ nguyên giá ở từng quốc gia. Thêm một đề xuất dưới dạng geo-pricing round để chạy nó như một A/B test — có thể chạy đồng thời tối đa 5 round. [Tìm hiểu thêm](autopilot-growth-plan#geo-pricing-hypotheses) - **Tự động hóa cụm từ tìm kiếm trong Apple Ads Manager**: Tự động đưa các cụm từ tìm kiếm hiệu quả lên thành từ khóa khớp chính xác và loại trừ chúng tại nguồn — không cần tải xuống báo cáo thủ công. Có thể tạo quy tắc từ các mẫu có sẵn hoặc xây dựng từ đầu với các điều kiện và lịch trình tùy chỉnh. [Tìm hiểu thêm](ads-manager-automations-search-terms) - **Maximize Conversions bidding trong Apple Ads Manager**: Khi tạo chiến dịch, bạn có thể chọn Maximize Conversions làm chiến lược đặt giá thầu. Thuật toán của Apple tối đa hóa lượt tải xuống trong ngân sách của bạn, dựa trên CPA mục tiêu tùy chọn. [Tìm hiểu thêm](ads-manager-create-campaign) - **Tích hợp FunnelFox trong Adapty UA**: Tích hợp mới với FunnelFox hiện đã có trong Adapty UA. [FunnelFox](ua-funnelfox) - **Tài liệu bằng tiếng Trung**: Tài liệu Adapty hiện đã có bằng tiếng Trung (中文). Chuyển đổi ngôn ngữ bằng bộ chọn ngôn ngữ trên thanh điều hướng trên cùng. ## Tháng 2 năm 2026 \{#february-2026\} - **Giá sản phẩm theo từng quốc gia**: Đặt mức giá khác nhau cho từng quốc gia trực tiếp trên Adapty Dashboard — Adapty tự động đồng bộ thay đổi với App Store Connect và Google Play. Mọi cập nhật giá đều được ghi lại trong nhật ký kiểm tra, đảm bảo không có thay đổi nào bị bỏ sót. [Tìm hiểu thêm](edit-product) - **Giá của đối thủ cạnh tranh theo quốc gia trong Autopilot**: So sánh giá gói đăng ký của bạn với đối thủ cạnh tranh tại các thị trường trọng điểm. [Tìm hiểu thêm](autopilot-analysis#market-and-competitor-analysis) - **Kiểm soát phiên bản onboarding**: Theo dõi và quản lý các phiên bản onboarding với lịch sử phiên bản đầy đủ. Xem lại các thay đổi và khôi phục khi cần. - **Biểu đồ chuyển đổi paywall trong analytics**: Hai biểu đồ chuyển đổi mới — Paywall view → Trial và Paywall view → Paid — cho thấy paywall của bạn chuyển đổi người xem thành người đăng ký như thế nào. [Tìm hiểu thêm](analytics-conversion) - **Nhân bản phân khúc**: Sao chép một phân khúc hiện có cùng với toàn bộ bộ lọc thay vì xây dựng lại từ đầu. Hữu ích khi chạy nhiều chiến dịch hoặc A/B test với các đối tượng trùng lặp. [Tìm hiểu thêm](segments#duplicate-segments) - **Thông báo đẩy trong ứng dụng di động Adapty**: Cấu hình thông báo đẩy cho 14 loại sự kiện trực tiếp trong ứng dụng Adapty iOS để theo dõi hoạt động gói đăng ký mà không cần mở dashboard. [Tìm hiểu thêm](push-notifications) - **Kotlin Multiplatform SDK 3.15**: Thêm hỗ trợ onboarding, web paywall, và cải tiến API. [Tìm hiểu thêm](migration-to-kmp-315) - **Capacitor SDK 3.16**: Thêm hỗ trợ Capacitor 8. Các dự án đang dùng Capacitor 7 nên ở lại SDK v3.15. [Tìm hiểu thêm](migration-to-capacitor-316) - **Hướng dẫn tích hợp SDK có sự hỗ trợ của LLM**: Hướng dẫn từng bước tích hợp Adapty với sự hỗ trợ của các AI coding assistant. Mỗi hướng dẫn sẽ dẫn dắt LLM của bạn qua toàn bộ quá trình triển khai, từ thiết lập dashboard đến xử lý mua hàng. [iOS](adapty-cursor) | [Android](adapty-cursor-android) | [React Native](adapty-cursor-react-native) | [Flutter](adapty-cursor-flutter) | [Unity](adapty-cursor-unity) | [Kotlin Multiplatform](adapty-cursor-kmp) | [Capacitor](adapty-cursor-capacitor). Để có flow tự động chỉ với một lệnh, hãy thử **adapty-sdk-integration skill** mới (beta): [iOS](adapty-sdk-integration-skill) | [Android](adapty-sdk-integration-skill-android) | [React Native](adapty-sdk-integration-skill-react-native) | [Flutter](adapty-sdk-integration-skill-flutter) | [Unity](adapty-sdk-integration-skill-unity) | [Kotlin Multiplatform](adapty-sdk-integration-skill-kmp) | [Capacitor](adapty-sdk-integration-skill-capacitor) ## Tháng 1 năm 2026 \{#january-2026\} - **Capacitor SDK chính thức phát hành**: Capacitor SDK hiện đã sẵn sàng cho môi trường production sau quá trình kiểm thử toàn diện. Xây dựng ứng dụng gói đăng ký cho iOS và Android bằng Capacitor với hỗ trợ tích hợp Adapty đầy đủ. [Tìm hiểu thêm](capacitor-sdk-overview) - **Autopilot cho ứng dụng mới**: Phân tích Autopilot hiện khả dụng ngay cả khi ứng dụng của bạn chưa có lịch sử giao dịch phong phú. Nhận các đề xuất tối ưu hóa giá dựa trên dữ liệu và xây dựng kế hoạch tăng trưởng ngay từ ngày đầu. [Tìm hiểu thêm](autopilot) - **Cơ hội định giá toàn cầu trong Autopilot**: Xác định tiềm năng doanh thu trên các thị trường hoạt động tốt nhất của bạn với các đề xuất định giá theo từng quốc gia. Autopilot phân tích tỷ lệ chuyển đổi và sức mua cho 5 quốc gia hàng đầu tiếp theo của bạn, cung cấp thông tin dựa trên dữ liệu về việc nên tăng, giảm hay duy trì giá dựa trên Chỉ số Giá Adapty. [Tìm hiểu thêm](autopilot) - **Chỉ số chuyển đổi khôi phục thanh toán**: Các biểu đồ phân tích mới theo dõi doanh thu được khôi phục từ các vấn đề thanh toán và thời gian ân hạn. Theo dõi "Billing issue converted", "Billing issue converted revenue", "Grace period converted" và "Grace period converted revenue" để đo lường hiệu quả khôi phục giữ chân người dùng của bạn. - **Quản lý quảng cáo trực tiếp trong Apple Ads Manager**: Tạo và quản lý các chiến dịch Apple Ads của bạn ngay trong Adapty mà không cần chuyển đổi giữa các nền tảng. [Tìm hiểu thêm](ads-manager-manage-ads) - **Phân tích Apple Ads Manager**: Truy cập các chỉ số hiệu suất chi tiết ở cấp độ quảng cáo và dữ liệu attribution trong Adapty. Xem hiệu suất chiến dịch, phân tích nhóm quảng cáo và thông tin attribution trong một dashboard thống nhất. [Tìm hiểu thêm](adapty-ads-manager-analytics) - **Biểu đồ attribution Apple Ads**: Kết hợp nhiều chỉ số attribution trong các biểu đồ tùy chỉnh để phân tích hiệu suất Apple Ads của bạn cùng với dữ liệu gói đăng ký. [Tìm hiểu thêm](adapty-ads-manager-analytics#charts) - **Phân khúc attribution Apple Ads**: Tạo phân khúc người dùng dựa trên dữ liệu attribution từ Apple Ads với quy trình chỉ hai bước. Nhắm mục tiêu người dùng theo chiến dịch, nhóm quảng cáo hoặc từ khóa để phân tích và thử nghiệm chính xác hơn. [Tìm hiểu thêm](ads-manager-create-segments) - **Nền tảng tài liệu mới**: Trang tài liệu đã được migrate sang nền tảng mới, cho phép cập nhật tính năng nhanh hơn và cải thiện trải nghiệm người dùng với tính năng tìm kiếm, điều hướng và tổ chức nội dung được nâng cao. ## Tháng 12 năm 2025 \{#december-2025\} - **Tài liệu Apple Ads Manager**: Kết hợp dữ liệu chiến dịch Apple Search Ads với các chỉ số doanh thu trong một dashboard phân tích duy nhất. Tài liệu mới bao gồm việc tạo chiến dịch, quản lý nhóm quảng cáo và cách theo dõi ROI chi tiêu quảng cáo cùng với hiệu suất gói đăng ký. [Tìm hiểu thêm](ads-manager) - **Paywall web trong ứng dụng**: Hiển thị paywall dạng web ngay trong ứng dụng của bạn thông qua trình duyệt in-app, mang lại trải nghiệm liền mạch mà không cần chuyển hướng ra ngoài. [iOS](ios-web-paywall#open-web-paywalls-in-an-in-app-browser) | [Android](android-web-paywall#open-web-paywalls-in-an-in-app-browser) | [React Native](react-native-web-paywall#open-web-paywalls-in-an-in-app-browser) | [Flutter](flutter-web-paywall#open-web-paywalls-in-an-in-app-browser) - **Phân khúc cuộn (Rolling segments)**: Tạo các phân khúc đối tượng động tự động cập nhật dựa trên cửa sổ thời gian di chuyển. Ví dụ: tạo phân khúc "người dùng đã cài đặt ứng dụng trong 7 ngày qua" và phân khúc này sẽ liên tục làm mới để luôn hiển thị những khách hàng mới nhất của bạn. [Tìm hiểu thêm](segments#available-attributes) - **Hướng dẫn thiết lập chiến dịch Meta và TikTok**: Tài liệu từng bước để tạo và theo dõi chiến dịch trên Meta (Facebook & Instagram) và TikTok, tích hợp theo dõi chuyển đổi và phân tích. [Meta](meta-create-campaign) | [TikTok](tiktok-create-campaign) - **Hướng dẫn nhanh triển khai paywall thủ công**: Tích hợp in-app purchase nhanh hơn với các hướng dẫn từng bước, chỉ cách tích hợp Adapty SDK vào giao diện paywall tùy chỉnh của bạn. [iOS](ios-implement-paywalls-manually) | [Android](android-implement-paywalls-manually) | [React Native](react-native-implement-paywalls-manually) | [Flutter](flutter-implement-paywalls-manually) | [Unity](unity-implement-paywalls-manually) | [Kotlin Multiplatform](kmp-quickstart-manual) | [Capacitor](capacitor-quickstart-manual) - **Trình duyệt trong ứng dụng cho liên kết onboarding**: Các liên kết bên ngoài trong onboarding giờ đây mặc định mở trong trình duyệt tích hợp, giữ người dùng ở lại ứng dụng của bạn. Bạn có thể tùy chỉnh hành vi này để sử dụng trình duyệt ngoài nếu cần. [iOS](ios-present-onboardings#customize-how-links-open-in-onboardings) | [Android](android-present-onboardings#customize-how-links-open-in-onboardings) | [React Native](react-native-present-onboardings#customize-how-links-open-in-onboardings) - **Cải thiện gợi ý Autopilot**: Autopilot giờ đây đưa ra khuyến nghị tối ưu hóa giá tốt hơn nhờ phân tích dữ liệu gói đăng ký của bạn sâu hơn. [Thử Autopilot](autopilot) - **Chế độ tối cho tài liệu**: Tài liệu giờ hỗ trợ chế độ tối với tính năng tự động nhận diện cài đặt hệ thống hoặc chuyển đổi thủ công ở góc trên bên phải. --- # File: generate-in-app-purchase-key --- --- title: "Tạo In-App Purchase Key trong App Store Connect" description: "Tạo in-app purchase key để xác thực giao dịch an toàn." --- **In-App Purchase Key** là một API key chuyên dụng được tạo trong App Store Connect để xác thực các giao dịch mua hàng bằng cách xác nhận tính xác thực của chúng. :::note Để tạo API key cho App Store Server API, bạn phải có vai trò Admin hoặc Account Holder trong App Store Connect. Bạn cũng có thể đọc thêm về cách tạo API Key trong [Tài liệu Apple Developer](https://developer.apple.com/documentation/appstoreserverapi/creating-api-keys-to-authorize-api-requests). ::: 1. Mở **App Store Connect**. Truy cập vào mục [**Users and Access** → **Integrations** → **In-App Purchase**](https://appstoreconnect.apple.com/access/integrations/api/subs). 2. Sau đó nhấn vào nút thêm **(+)** bên cạnh tiêu đề **Active**.
3. Trong cửa sổ **Generate In-App Purchase Key** vừa mở, nhập tên cho key để bạn dễ nhận biết sau này. Tên này sẽ không được dùng trong Adapty.
4. Nhấn nút **Generate**. Sau khi cửa sổ **Generate in-App Purchase Key** đóng lại, bạn sẽ thấy key vừa tạo xuất hiện trong danh sách **Active**.
5. Sau khi đã tạo API key, nhấn nút **Download In-App Purchase Key** để tải key về dưới dạng file.
6. Trong cửa sổ **Download in-App Purchase Key**, nhấn nút **Download**. File sẽ được lưu vào máy tính của bạn.
Điều quan trọng là phải giữ file này an toàn để tải lên Adapty Dashboard sau này. Lưu ý rằng file được tạo ra chỉ có thể tải xuống một lần duy nhất, vì vậy hãy đảm bảo lưu trữ an toàn cho đến khi bạn tải lên. File .p8 key được tạo từ mục **In-App Purchase** sẽ được sử dụng khi [cấu hình tích hợp ban đầu của Adapty với App Store](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file).
**Tiếp theo:**
- [Cấu hình tích hợp App Store](app-store-connection-configuration)
---
# File: app-store-connection-configuration
---
---
title: "Cấu hình tích hợp App Store"
description: "Cấu hình kết nối App Store của bạn để theo dõi gói đăng ký liền mạch."
---
3. Sao chép **Issuer ID** và dán vào trường **In-app purchase Issuer ID** trên Adapty Dashboard.
4. Sao chép **Key ID** và dán vào trường **In-app purchase Key ID** trên Adapty Dashboard.
## Bước 3. Tải lên file In-App Purchase Key \{#step-3-upload-in-app-purchase-key-file\}
Tải lên file **In-App Purchase Key** bạn đã tải xuống ở phần [Tạo In-App Purchase Key trong App Store Connect](generate-in-app-purchase-key)
vào trường **Private key (.p8 file)** trên Adapty Dashboard.
## Bước 4. Đối với trial và ưu đãi đặc biệt – thiết lập promotional offers \{#step-4-for-trials-and-special-offers--set-up-promotional-offers\}
:::important
Bước này bắt buộc nếu ứng dụng của bạn có [trial hoặc các ưu đãi khác](offers).
:::
1. Sao chép key ID bạn đã dùng ở [Bước 2](#step-2-provide-issuer-id-and-key-id) vào trường **Subscription key ID** trong mục **App Store promotional offers**.
2. Tải lên file **In-App Purchase Key** bạn đã dùng ở [Bước 3](#step-3-upload-in-app-purchase-key-file) vào khu vực **Subscription key (.p8 file)** trong mục **App Store promotional offers**.
## Bước 5. Nhập App Store shared secret \{#step-5-enter-app-store-shared-secret\}
**App Store shared secret** (còn được gọi là App Store Connect Shared Secret) là một chuỗi thập lục phân 32 ký tự dùng để xác thực in-app purchase và receipt của gói đăng ký.
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 mục **General** → **App Information**.
2. Cuộn xuống phần **App-Specific Shared Secret**.
:::info
Nếu không thấy phần **App-Specific Shared Secret**, hãy đảm bảo bạn có vai trò Account Holder hoặc Admin. Nếu bạn có vai trò Admin nhưng vẫn không thấy phần này, hãy yêu cầu Account Holder của ứng dụng (người đã tạo ứng dụng trong App Store Connect) tạo App Store shared secret. Sau đó, phần này sẽ hiển thị với các Admin khác.
:::
3. Nhấp vào nút **Manage**.
4. Trong cửa sổ **App-Specific Shared Secret** vừa mở, sao chép **Shared Secret**. Nếu chưa thấy shared secret, hãy nhấp vào nút **Manage** hoặc **Generate** (tùy nút nào đang hiển thị), sau đó sao chép **Shared Secret**.
5. Dán **Shared Secret** vừa sao chép vào trường **App Store shared secret** trên Adapty Dashboard.
6. Nhấp vào nút **Save** trên Adapty Dashboard để xác nhận các thay đổi.
## Bước 6. Thêm App Store Connect API key \{#step-6-add-app-store-connect-api-key\}
Tạo App Store Connect API key và thêm vào Adapty để có thể [quản lý sản phẩm trên App Store từ Adapty dashboard](create-product#create-product-and-push-to-store):
1. Trong App Store Connect, đi tới [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api) và nhấp **+**.
2. Trong cửa sổ **Generate API key**, nhập tên cho khóa và cấp quyền truy cập **Admin**.
3. Nhấp **Download** bên cạnh khóa của bạn. Lưu ý rằng bạn chỉ có thể tải xuống một lần.
4. Trên Adapty dashboard, đi tới [**App settings > iOS SDK**](https://app.adapty.io/settings/ios-sdk) và nhấp **Connect API key**.
5. Điền vào các trường trong cửa sổ:
- **Issuer ID**: Sao chép từ [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api). Nó nằm phía trên bảng **API keys**.
- **Key ID**: Sao chép từ [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api). Nó nằm trong bảng **API keys** bên cạnh khóa của bạn.
- **API key**: Tải lên file API key bạn đã tải xuống từ App Store Connect.
6. Nhấp **Connect**.
**Bước tiếp theo**
- [Bật thông báo máy chủ App Store](enable-app-store-server-notifications)
---
# File: enable-app-store-server-notifications
---
---
title: "Bật thông báo máy chủ App Store"
description: "Bật thông báo máy chủ App Store để theo dõi các sự kiện gói đăng ký theo thời gian thực."
---
Việc thiết lập thông báo máy chủ App Store rất quan trọng để đảm bảo độ chính xác của dữ liệu, vì nó cho phép bạn nhận cập nhật tức thì từ App Store, bao gồm thông tin về hoàn tiền và các sự kiện khác.
:::important
Cần có Adapty iOS SDK 2.10.0 trở lên để hỗ trợ đầy đủ App Store Server Notifications V2.
:::
1. Sao chép **URL for App Store server notification** trong Adapty Dashboard.
2. Mở [App Store Connect](https://appstoreconnect.apple.com/apps). Chọn ứng dụng của bạn và vào mục **General** → **App Information**, phần **App Store Server Notifications**.
3. Dán **URL for App Store server notification** đã sao chép vào các trường **Production Server URL** và **Sandbox Server URL**.
## Chuyển tiếp sự kiện thô \{#raw-events-forwarding\}
Đôi khi bạn vẫn muốn nhận các sự kiện S2S thô từ Apple. Để tiếp tục nhận chúng khi đang dùng Adapty, chỉ cần thêm endpoint của bạn vào trường **URL for forwarding raw Apple events**, và chúng tôi sẽ gửi các sự kiện thô nguyên bản từ Apple đến bạn.
**Tiếp theo**
Thiết lập Adapty SDK cho:
- [iOS](sdk-installation-ios)
- [React Native](sdk-installation-reactnative)
- [Flutter](sdk-installation-flutter)
- [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform)
- [Unity](sdk-installation-unity)
---
# File: troubleshoot-app-store-integration
---
---
title: "Xử lý sự cố tích hợp App Store"
description: "Giải quyết các sự cố thiết lập Apple App Store phổ biến — thỏa thuận chờ xử lý, độ trễ thông báo máy chủ và không khớp giá."
---
Bài viết này đề cập đến các sự cố tích hợp App Store phổ biến. Mỗi phần dưới đây liệt kê triệu chứng, nguyên nhân gốc rễ và cách giải quyết.
## Sản phẩm không hiển thị \{#products-dont-appear\}
Hai triệu chứng bề mặt chỉ đến cùng một nguyên nhân gốc rễ:
- Khóa API App Store Connect được thiết lập đúng, nhưng Adapty không thể lấy sản phẩm.
- Sản phẩm tồn tại trong App Store Connect nhưng không hiển thị trong Adapty, hoặc hiển thị ít hơn dự kiến. SDK báo lỗi "Product Id not found" khi thực hiện mua hàng.
Nguyên nhân phổ biến nhất là **thỏa thuận Apple chưa được ký** — thỏa thuận thanh toán, biểu mẫu thuế hoặc biểu mẫu ngân hàng đang ở trạng thái chờ xử lý hoặc chưa ký. Khi các thỏa thuận đang chờ xử lý, App Store Connect API sẽ trả về lỗi 403 một cách im lặng trên các endpoint liên quan đến sản phẩm. Không có lỗi rõ ràng nào hiển thị với Adapty; các sản phẩm bị lọc ra một cách âm thầm.
Truy cập **App Store Connect → Agreements, Tax, and Banking** và ký tất cả các thỏa thuận đang chờ xử lý. Sau đó đồng bộ lại trong **App settings → iOS SDK** của Adapty.
## Thông báo máy chủ App Store hiển thị "Delayed" \{#app-store-server-notifications-show-delayed\}
Trong App Store Connect, trạng thái App Store Server Notifications có thể hiển thị là **Delayed**. Điều này có nghĩa là Apple đang bị trễ trong việc gửi thông báo sự kiện gói đăng ký — các sự kiện gia hạn, hủy và vấn đề thanh toán đang được xếp hàng và đến muộn.
Thống kê lượt cài đặt không bị ảnh hưởng. Adapty đếm lượt cài đặt từ lần khởi chạy ứng dụng đầu tiên, không phải từ thông báo phía máy chủ.
Nếu dữ liệu gia hạn hoặc hủy bị trễ, trạng thái Delayed là nguyên nhân có khả năng cao nhất. Trạng thái này thường tự động được xóa khi Apple xử lý hết hàng đợi.
## Giá trong Adapty không khớp với App Store \{#prices-in-adapty-dont-match-app-store\}
Trường **price** trên trang chỉnh sửa sản phẩm của Adapty hoạt động khác nhau tùy thuộc vào cách sản phẩm được thêm vào.
Nếu bạn tạo sản phẩm trong Adapty và đẩy lên cửa hàng từ dashboard, giá này được dùng làm giá cửa hàng ban đầu.
Nếu bạn thêm một sản phẩm đã tồn tại trong cửa hàng, giá này chỉ là giá tạm thời. Phân tích, tích hợp và SDK của Adapty đều sử dụng giá thực tế được lấy từ App Store. Những thay đổi về giá trên App Store không đồng bộ lại để cập nhật giá tạm thời này, và hiện tại bạn không thể chỉnh sửa giá tạm thời từ dashboard.
## Xuất giá CSV trống \{#csv-price-export-is-empty\}
Nếu file CSV xuất giá của bạn chỉ trả về tiêu đề cột, khóa API App Store Connect của bạn chưa được cấu hình đầy đủ. Xem [Bước 6 — Thêm khóa API App Store Connect](app-store-connection-configuration#step-6-add-app-store-connect-api-key).
## Không thể đẩy sản phẩm mới lên App Store \{#cant-push-new-products-to-app-store\}
Adapty có thể đẩy sản phẩm mới lên App Store Connect khi bạn tạo chúng trong dashboard. Tùy chọn đẩy bị chặn nếu tích hợp App Store của bạn chưa được cấu hình đầy đủ. Hai cài đặt sau là bắt buộc:
- **Apple app ID**: Cấu hình tại [Bước 1 — Cung cấp Bundle ID và Apple app ID](app-store-connection-configuration#step-1-provide-bundle-id-and-apple-app-id).
- **App Store Connect API key**: Cấu hình tại [Bước 6 — Thêm khóa API App Store Connect](app-store-connection-configuration#step-6-add-app-store-connect-api-key).
---
# File: enabling-of-devepoler-api
---
---
title: "Bật Developer APIs trong Google Play Console"
description: "Bật Developer API của Adapty để tự động hóa và tối ưu hóa việc quản lý gói đăng ký trong ứng dụng của bạn."
---
3. Mở trang [**Google Play Android Developer API**](https://console.cloud.google.com/apis/library/androidpublisher.googleapis.com).
4. Nhấn nút **Enable** và chờ đến khi trạng thái **Enabled** hiện ra. Điều này có nghĩa là Google Android Developer API đã được bật.
5. Mở trang [**Google Play Developer Reporting API**](https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com).
6. Nhấn nút **Enable** và chờ đến khi trạng thái **Enabled** hiện ra.
7. Mở trang [**Cloud Pub/Sub API**](https://console.cloud.google.com/marketplace/product/google/pubsub.googleapis.com).
8. Nhấn nút **Enable** và chờ đến khi trạng thái **Enabled** hiện ra.
Developer APIs đã được bật.
Bạn có thể kiểm tra lại trên trang [**APIs & Services**](https://console.cloud.google.com/apis/dashboard) của Google Cloud Console. Cuộn trang xuống và xác nhận bảng ở cuối trang có đủ 3 API sau:
- Google Play Android Developer API
- Google Play Developer Reporting API
- Cloud Pub/Sub API
**Bước tiếp theo**
- [Tạo service account trong Google Cloud Console](create-service-account)
---
# File: create-service-account
---
---
title: "Tạo tài khoản dịch vụ trong Google Cloud Console"
description: "Tìm hiểu cách tạo tài khoản dịch vụ để truy cập API an toàn trong Adapty."
---
Để Adapty tự động hóa việc truy cập dữ liệu, cần có một tài khoản dịch vụ trong Google Play Console.
1. Mở mục [**IAM & Admin** -> **Service accounts**](https://console.cloud.google.com/iam-admin/serviceaccounts) trong Google Cloud Console. Hãy đảm bảo bạn đang dùng đúng project.
2. Trong cửa sổ **Service accounts**, nhấn nút **Create service account**.
3. Trong phần con **Service account details** của cửa sổ **Create service account**, nhập **Service Account Name** tùy ý. Chúng tôi khuyên bạn nên đặt tên có chứa "Adapty" để dễ nhận biết mục đích của tài khoản này. **Service account ID** sẽ được tạo tự động.
4. Sao chép địa chỉ email của tài khoản dịch vụ và lưu lại để dùng sau.
5. Nhấn nút **Create and continue**.
6. Trong danh sách thả xuống **Select a role** của phần con **Grant this service account access to project**, chọn **Pub/Sub -> Pub/Sub Admin**. Vai trò này cần thiết để bật thông báo nhà phát triển theo thời gian thực.
7. Nhấn nút **Add another role**.
8. Trong danh sách thả xuống **Role** mới xuất hiện, chọn **Monitoring -> Monitoring Viewer**. Vai trò này cần thiết để cho phép giám sát hàng đợi thông báo.
9. Nhấn nút **Continue**.
10. Nhấn nút **Done** mà không thay đổi gì. Cửa sổ **Service accounts** sẽ mở ra.
**Tiếp theo**
- [Cấp quyền cho tài khoản dịch vụ trong Google Play Console](grant-permissions-to-service-account)
---
# File: grant-permissions-to-service-account
---
---
title: "Cấp quyền cho service account trong Google Play Console"
description: "Cấp quyền cho service account để truy cập API an toàn và hiệu quả."
---
Cấp các quyền cần thiết cho service account mà Adapty sẽ sử dụng để quản lý gói đăng ký và xác thực các giao dịch mua.
1. Mở trang [**Users and permissions**](https://play.google.com/console/u/0/developers/8970033217728091060/users-and-permissions) trong Google Play Console và nhấn nút **Invite new users**.
2. Trong trang **Invite user**, nhập email của service account bạn đã tạo.
3. Chuyển sang tab **Account permissions**.
4. Chọn các quyền sau:
- View app information and download bulk reports (read-only)
- View financial data, orders, and cancellation survey responses
- Manage orders and subscriptions
- Manage store presence
5. Nhấn nút **Invite user**.
6. Trong cửa sổ **Send invite?**, nhấn nút **Send invite**. Service account sẽ xuất hiện trong danh sách người dùng.
**Bước tiếp theo**
- [Tạo file khóa service account trong Google Play Console](create-service-account-key-file)
---
# File: create-service-account-key-file
---
---
title: "Tạo file khóa tài khoản dịch vụ trong Google Play Console"
description: "Tìm hiểu cách tạo file khóa tài khoản dịch vụ để tích hợp liền mạch với Adapty."
---
Để liên kết ứng dụng di động của bạn trên Play Store với Adapty, bạn cần tạo các file khóa tài khoản dịch vụ đặc biệt trong Google Play Console và tải chúng lên Adapty. Các file này giúp bảo mật ứng dụng và ngăn chặn truy cập trái phép.
:::warning
Thông thường, tài khoản dịch vụ mới cần ít nhất 24 giờ để được kích hoạt. Tuy nhiên, có một [mẹo](https://stackoverflow.com/a/60691844) như sau: Sau khi tạo tài khoản dịch vụ trong [Google Play Console](https://play.google.com/apps/publish/), mở bất kỳ ứng dụng nào và điều hướng đến **Monetize** -> **Products** -> **Subscriptions/In-app products**. Chỉnh sửa mô tả của bất kỳ sản phẩm nào rồi lưu lại. Thao tác này sẽ kích hoạt tài khoản dịch vụ ngay lập tức, và bạn có thể hoàn tác các thay đổi sau đó.
:::
1. Mở mục [**Service accounts**](https://console.cloud.google.com/iam-admin/serviceaccounts) trong Google Play Console. Đảm bảo bạn đã chọn đúng dự án.
2. Trong cửa sổ hiện ra, nhấp vào **Add key** và chọn **Create new key** từ menu thả xuống.
3. Trong cửa sổ **Create private key for [Your_project_name]**, nhấp vào **Create**. Khóa riêng tư của bạn sẽ được lưu vào máy tính dưới dạng file JSON. Bạn có thể tìm thấy nó bằng tên file được hiển thị trong cửa sổ **Private key saved to your computer**.
4. Trong cửa sổ **Create private key for Your_project_name**, nhấp nút **Create**. Thao tác này sẽ lưu khóa riêng tư của bạn vào máy tính dưới dạng file JSON. Bạn có thể dùng tên file được cung cấp trong cửa sổ **Private key saved to your computer** để tìm lại file đó khi cần.
Bạn sẽ cần file này khi [cấu hình tích hợp Google Play Store](google-play-store-connection-configuration).
:::warning
Thông thường, tài khoản dịch vụ mới cần ít nhất 24 giờ để được kích hoạt. Tuy nhiên, có một [mẹo](https://stackoverflow.com/a/60691844) như sau: Sau khi tạo tài khoản dịch vụ trong [Google Play Console](https://play.google.com/apps/publish/), mở bất kỳ ứng dụng nào và điều hướng đến **Monetize** -> **Products** -> **Subscriptions/In-app products**. Chỉnh sửa mô tả của bất kỳ sản phẩm nào rồi lưu lại. Thao tác này sẽ kích hoạt tài khoản dịch vụ ngay lập tức, và bạn có thể hoàn tác các thay đổi sau đó.
:::
**Tiếp theo**
- [Cấu hình tích hợp Google Play Store](google-play-store-connection-configuration)
---
# File: google-play-store-connection-configuration
---
---
title: "Cấu hình tích hợp Google Play Store"
description: "Cấu hình kết nối Google Play Store trong Adapty để xử lý in-app purchase suôn sẻ."
---
Phần này mô tả quy trình tích hợp ứng dụng di động của bạn được bán qua Google Play với Adapty. Bạn cần nhập dữ liệu cấu hình ứng dụng từ Play Store vào Adapty Dashboard. Bước này rất quan trọng để xác thực các giao dịch mua và nhận cập nhật gói đăng ký từ Play Store trong Adapty.
Bạn có thể hoàn tất quá trình này trong lần onboarding đầu tiên hoặc thay đổi sau trong **App Settings** của Adapty Dashboard.
:::danger
Chỉ được phép thay đổi cấu hình trước khi bạn phát hành ứng dụng di động tích hợp Adapty paywall. Việc thay đổi sau khi phát hành sẽ phá vỡ tích hợp và các paywall sẽ ngừng hiển thị trong ứng dụng di động của bạn.
:::
## Bước 1. Cung cấp Package name \{#step-1-provide-package-name\}
Package name là định danh duy nhất của ứng dụng trên Google Play Store. Đây là yêu cầu bắt buộc cho các chức năng cơ bản của Adapty, chẳng hạn như xử lý gói đăng ký.
1. Mở [Google Play Developer Console](https://play.google.com/console/u/0/developers).
2. Chọn ứng dụng mà bạn cần lấy ID. Cửa sổ **Dashboard** sẽ mở ra.
3. Tìm product ID bên dưới tên ứng dụng và sao chép nó.
4. Mở [**App settings**](https://app.adapty.io/settings/android-sdk) từ menu trên cùng của Adapty.
5. Trong tab **Android SDK** của cửa sổ **App settings**, dán **Package name** vừa sao chép vào.
## Bước 2. Tải lên tệp khóa tài khoản \{#step-2-upload-the-account-key-file\}
1. Tải lên tệp khóa riêng tư của tài khoản dịch vụ ở định dạng JSON mà bạn đã tạo ở bước [Tạo tệp khóa tài khoản dịch vụ](create-service-account) vào khu vực **Service account key file**.
Đừng quên nhấn nút **Save** để xác nhận các thay đổi.
**Tiếp theo**
- [Bật Real-time developer notifications (RTDN) trong Google Play Console](enable-real-time-developer-notifications-rtdn)
---
# File: enable-real-time-developer-notifications-rtdn
---
---
title: "Bật thông báo nhà phát triển theo thời gian thực (RTDN) trong Google Play Console"
description: "Luôn được thông báo về các sự kiện quan trọng và đảm bảo độ chính xác của dữ liệu bằng cách bật Thông báo nhà phát triển theo thời gian thực (RTDN) trong Google Play Console cho Adapty. Tìm hiểu cách thiết lập RTDN để nhận cập nhật tức thì về hoàn tiền và các sự kiện quan trọng khác từ Play Store"
---
Việc thiết lập thông báo nhà phát triển theo thời gian thực (RTDN) rất quan trọng để đảm bảo độ chính xác của dữ liệu, vì nó cho phép bạn nhận cập nhật tức thì từ Play Store, bao gồm thông tin về hoàn tiền và các sự kiện khác.
## Bật thông báo \{#enable-notifications\}
1. Đảm bảo bạn đã bật **Google Cloud Pub/Sub**. Mở [liên kết này](https://console.cloud.google.com/flows/enableapi?apiid=pubsub) và chọn dự án ứng dụng của bạn. Nếu chưa bật **Google Cloud Pub/Sub**, bạn phải thực hiện tại đây.
2. Vào [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) từ menu trên cùng của Adapty và sao chép nội dung trong trường **Enable Pub/Sub API** bên cạnh tiêu đề **Google Play RTDN topic name**.
:::note Nếu nội dung trong trường **Enable Pub/Sub API** có định dạng sai (định dạng đúng bắt đầu bằng `projects/...`), hãy tham khảo phần [Sửa định dạng sai trong trường Enable Pub/Sub API](enable-real-time-developer-notifications-rtdn#fixing-incorrect-format-in-enable-pubsub-api-field) để được hỗ trợ. ::: 3. Mở [Google Play Console](https://play.google.com/console/), chọn ứng dụng của bạn, rồi vào **Monetize with Play** -> **Monetization setup**. Trong phần **Google Play Billing**, chọn hộp kiểm **Enable real-time notifications**. 4. Dán nội dung của trường **Enable Pub/Sub API** mà bạn đã sao chép trong **App Settings** của Adapty vào trường **Topic name**. 5. Nhấp **Save changes** trong Google Play Console.
## Kiểm tra thông báo \{#test-notifications\}
Để kiểm tra xem bạn đã đăng ký nhận thông báo nhà phát triển theo thời gian thực thành công chưa:
1. Lưu các thay đổi trong cài đặt Google Play Console.
2. Bên dưới **Topic name** trong Google Play Console, nhấp **Send test notification**.
3. Vào [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) trong Adapty. Nếu thông báo kiểm tra đã được gửi, bạn sẽ thấy trạng thái của nó phía trên tên topic.
## Sửa định dạng sai trong trường Enable Pub/Sub API \{#fixing-incorrect-format-in-enable-pubsub-api-field\}
Nếu nội dung trong trường **Enable Pub/Sub API** có định dạng sai (định dạng đúng bắt đầu bằng `projects/...`), hãy làm theo các bước sau để khắc phục sự cố:
### 1. Xác minh việc bật API và phân quyền \{#1-verify-api-enablement-and-permissions\}
Hãy đảm bảo cẩn thận rằng tất cả các API cần thiết đã được bật và quyền đã được cấp đúng cho service account. Dù bạn đã hoàn thành các bước này rồi, vẫn nên thực hiện lại để chắc chắn không bỏ sót bước nào. Lặp lại các bước trong các phần sau:
1. [Bật Developer APIs trong Google Play Console](enabling-of-devepoler-api)
2. [Tạo service account trong Google Cloud Console](create-service-account)
3. [Cấp quyền cho service account trong Google Play Console](grant-permissions-to-service-account)
4. [Tạo file khóa service account trong Google Play Console](create-service-account-key-file)
5. [Cấu hình tích hợp Google Play Store](google-play-store-connection-configuration)
### 2. Điều chỉnh chính sách Domain \{#2-adjust-domain-policies\}
Thay đổi chính sách **Domain restricted contacts** và **Domain restricted sharing**:
1. Mở [Google Cloud Console](https://console.cloud.google.com/) và chọn dự án mà bạn đã tạo service account để quản lý ứng dụng.
2. Trong phần **Quick Access**, chọn **IAM & Admin**.
3. Ở khung bên trái, chọn **Organization Policies**.
4. Tìm chính sách **Domain restricted contacts**.
5. Nhấp vào nút dấu ba chấm trong cột **Actions** và chọn **Edit policy**.
6. Trong cửa sổ chỉnh sửa chính sách:
1. Dưới **Policy source**, chọn radio button **Override parent's policy**.
2. Dưới **Policy enforcement**, chọn radio button **Replace**.
3. Dưới **Rules**, nhấp nút **ADD A RULE**.
4. Dưới **New rule** -> **Policy values**, chọn **Allow All**.
5. Nhấp **SET POLICY**.
7. Lặp lại các bước 4-6 cho chính sách **Domain restricted sharing**.
Cuối cùng, tạo lại nội dung của trường **Enable Pub/Sub API** bên cạnh tiêu đề **Google Play RTDN topic name**. Trường này sẽ có định dạng đúng.
Hãy nhớ chuyển **Policy source** trở lại **Inherit parent's policy** cho các chính sách đã cập nhật sau khi bạn đã bật thành công Thông báo nhà phát triển theo thời gian thực (RTDN).
## Chuyển tiếp sự kiện thô \{#raw-events-forwarding\}
Đôi khi bạn vẫn muốn nhận các sự kiện S2S thô từ Google. Để tiếp tục nhận chúng khi sử dụng Adapty, chỉ cần thêm endpoint của bạn vào trường **URL for forwarding raw Google events**, và chúng tôi sẽ chuyển tiếp các sự kiện thô nguyên bản từ Google.
---
**Tiếp theo**
Thiết lập Adapty SDK cho:
- [Android](sdk-installation-android)
- [React Native](sdk-installation-reactnative)
- [Flutter](sdk-installation-flutter)
- [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform)
- [Unity](sdk-installation-unity)
---
# File: stripe
---
---
title: "Tích hợp ban đầu với Stripe"
description: "Tích hợp Stripe với Adapty để xử lý thanh toán gói đăng ký liền mạch."
---
Adapty hỗ trợ luồng gói đăng ký web2app bằng cách theo dõi các khoản thanh toán và gói đăng ký web được thực hiện qua [Stripe](https://stripe.com/).
Tích hợp này bao gồm các giao dịch mua được khởi tạo từ web (Stripe Checkout, các trang thanh toán được lưu trữ, hoặc các luồng web tùy chỉnh) và đồng bộ hóa chúng với quyền truy cập ứng dụng di động và phân tích.
Tích hợp này hữu ích trong các trường hợp sau:
- Tự động cấp quyền truy cập vào các tính năng trả phí cho người dùng đã mua trên web nhưng sau đó cài đặt ứng dụng và đăng nhập vào tài khoản của họ
- Có toàn bộ phân tích gói đăng ký trong một Adapty Dashboard duy nhất (bao gồm cohort, dự đoán, và các công cụ phân tích khác của chúng tôi)
Mặc dù các giao dịch mua trên web đang ngày càng phổ biến với các ứng dụng, Apple App Store chỉ cho phép hệ thống khác ngoài in-app purchase đối với hàng hóa kỹ thuật số tại Hoa Kỳ. Hãy đảm bảo bạn không quảng bá gói đăng ký web bên trong ứng dụng của mình cho các quốc gia khác. Nếu không, ứng dụng của bạn có thể bị từ chối hoặc bị cấm.
Các bước dưới đây mô tả cách cấu hình tích hợp Stripe.
:::important
Tích hợp này tập trung vào việc theo dõi và đồng bộ hóa các giao dịch mua Stripe trên web. Nếu bạn cần chuyển người dùng từ ứng dụng sang một trang thanh toán web, hãy xem [Web paywalls](web-paywall).
:::
## 1\. Kết nối Stripe với Adapty \{#1-connect-stripe-to-adapty\}
Tích hợp này chủ yếu dựa vào việc Adapty lấy dữ liệu gói đăng ký từ Stripe qua webhook. Do đó, bạn cần kết nối tài khoản Adapty của mình với tài khoản Stripe bằng cách cung cấp API Keys và sử dụng URL webhook của Adapty trong Stripe. Để tự động hóa việc cấu hình webhook, hãy cài đặt ứng dụng Adapty trong Stripe:
:::note
Các bước dưới đây giống nhau cho cả chế độ Production và Test của Stripe, nhưng bạn cần sử dụng các API key khác nhau cho mỗi chế độ.
:::
0. Xác định xem bạn đang kết nối Stripe ở chế độ test hay live. Nếu ban đầu bạn thực hiện ở chế độ test, bạn sẽ cần lặp lại các bước dưới đây cho chế độ live.
1. Truy cập [Stripe App Marketplace](https://marketplace.stripe.com/apps/adapty) và cài đặt ứng dụng Adapty. Lưu ý rằng chế độ sandbox không hỗ trợ cài đặt ứng dụng. Bạn chỉ có thể thực hiện điều này ở chế độ production hoặc test.
2. Cấp cho ứng dụng các quyền cần thiết. Điều này cho phép Adapty truy cập dữ liệu và lịch sử gói đăng ký. Sau đó, nhấp vào **Continue to app settings** để tiếp tục.
Ở cuối pop-up quyền, bạn có thể chọn cài đặt ứng dụng ở chế độ live hay test.
3. Trong pop-up, tạo một restricted key mới. Bạn sẽ cần xác minh danh tính bằng email, Touch ID, hoặc security key. Sau khi tạo key, bạn sẽ không thể xem lại được nữa, vì vậy hãy lưu trữ an toàn trong trình quản lý mật khẩu hoặc secret store.
4. Sao chép key đã tạo từ pop-up và truy cập [App Settings → Stripe](https://app.adapty.io/settings/stripe) của Adapty. Dán key vào phần **Stripe App Restricted API Key** tùy theo chế độ của bạn. Lưu ý rằng bạn phải tạo các key khác nhau cho chế độ test và live.
Xong rồi! Tiếp theo, hãy tạo sản phẩm trên Stripe và thêm chúng vào Adapty.
2. Nhấp vào nút **Reveal live (test) key** bên cạnh tiêu đề **Secret key**, sao chép nó và truy cập [App Settings → Stripe](https://app.adapty.io/settings/stripe) của Adapty. Dán key vào đây:
3. Tiếp theo, sao chép Webhook URL từ cuối trang tương tự trong Adapty. Truy cập [**Developers** → **Webhooks**](https://dashboard.stripe.com/webhooks) trong Stripe và nhấp vào nút **Add endpoint**:
4. Dán webhook URL từ Adapty vào trường **Endpoint URL**. Sau đó chọn **Latest API version** trong trường **Version** của webhook. Tiếp theo chọn các sự kiện sau:
- charge.refunded
- customer.subscription.created
- customer.subscription.deleted
- customer.subscription.paused
- customer.subscription.resumed
- customer.subscription.updated
- invoice.created
- invoice.updated
- payment_intent.succeeded
5. Nhấn "Add endpoint" rồi nhấn "Reveal" bên dưới "Signing secret". Đây là key dùng để giải mã dữ liệu webhook phía Adapty, hãy sao chép nó sau khi hiển thị:
6. Cuối cùng, dán key này vào App Settings → Stripe của Adapty tại mục "Stripe Webhook Secret":
:::warning
Hiện tại Adapty chỉ hỗ trợ **Flat rate** ($9.99/tháng) hoặc **Package pricing** ($9.99/10 đơn vị), vì chúng hoạt động tương tự như các cửa hàng ứng dụng. Các tùy chọn **Tiered pricing**, **Usage-based fee** và **Customer chooses price** không được hỗ trợ
:::
## 3\. Thêm sản phẩm Stripe vào Adapty \{#3-add-stripe-products-to-adapty\}
:::warning
Sản phẩm là bắt buộc! Hãy chắc chắn tạo sản phẩm Stripe của bạn trong Adapty Dashboard. Adapty chỉ theo dõi các sự kiện cho các giao dịch được liên kết với những sản phẩm này, vì vậy đừng bỏ qua bước này—nếu không, các sự kiện giao dịch sẽ không được tạo.
:::
Chúng tôi xử lý Stripe tương tự như App Store và Google Play: đó chỉ là một cửa hàng khác nơi bạn bán sản phẩm kỹ thuật số. Vì vậy nó được cấu hình tương tự: chỉ cần thêm các sản phẩm Stripe (cụ thể là `product_id` và `price_id` của chúng) vào phần Products của Adapty:
Product ID trong Stripe trông giống như `prod_...` và price ID trông giống như `price_...`. Chúng khá dễ tìm cho mỗi sản phẩm trong [Product Catalog](https://dashboard.stripe.com/products?active=true) của Stripe, khi bạn mở bất kỳ sản phẩm nào:
Sau khi bạn đã thêm tất cả các sản phẩm cần thiết, bước tiếp theo là cho Stripe biết người dùng nào đang thực hiện giao dịch mua, để Adapty có thể nhận diện được!
## 4\. Bổ sung thông tin người dùng vào giao dịch mua trên web \{#4-enrich-purchases-made-on-the-web-with-your-user-id\}
Adapty dựa vào webhooks từ Stripe để cung cấp và cập nhật mức độ truy cập cho người dùng như là nguồn thông tin duy nhất. Nhưng bạn phải cung cấp thông tin bổ sung từ phía mình khi làm việc với Stripe để tích hợp này hoạt động đúng.
Để mức độ truy cập nhất quán trên các nền tảng (web hoặc di động), bạn phải đảm bảo có một ID người dùng duy nhất mà Adapty có thể nhận diện từ các webhook. Đây có thể là email, số điện thoại hoặc bất kỳ ID nào khác từ hệ thống xác thực bạn đang sử dụng.
Xác định ID bạn muốn dùng để nhận diện người dùng. Sau đó, truy cập phần code khởi tạo thanh toán qua Stripe — và thêm ID người dùng này vào object `metadata` của [Stripe Subscription](https://docs.stripe.com/api/subscriptions/object#subscription_object-metadata) (`sub_...`) hoặc [Checkout Session](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-metadata) (`ses_...`) với tên `customer_user_id` như sau:
```json showLineNumbers title="Stripe Metadata contents"
{'customer_user_id': "YOUR_USER_ID"}
```
Chỉ cần thêm một dòng đơn giản này là tất cả những gì bạn cần làm trong code. Sau đó, Adapty sẽ phân tích tất cả các webhook nhận được từ Stripe, trích xuất `metadata` này và liên kết chính xác các gói đăng ký với khách hàng của bạn.
:::warning
User ID là bắt buộc
Nếu không có, chúng tôi không có cách nào để khớp người dùng này và cấp cho họ mức độ truy cập trên di động.
Nếu bạn không cung cấp `customer_user_id` vào `metadata`, bạn sẽ có tùy chọn để Adapty tìm kiếm `customer_user_id` ở các vị trí khác: `email` từ Customer object của Stripe hoặc `client_reference_id` từ Session của Stripe.
Tìm hiểu thêm về cấu hình hành vi tạo hồ sơ người dùng [bên dưới](stripe#profile-creation-behavior)
:::
:::note
Customer trong Stripe cũng là bắt buộc
Nếu bạn đang sử dụng Checkout Sessions, [hãy đảm bảo bạn đang tạo Stripe Customer](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-customer_creation) bằng cách đặt `customer_creation` thành `always`.
:::
## 5\. Cấp quyền truy cập cho người dùng trên di động \{#5-provide-access-to-users-on-the-mobile\}
Để đảm bảo người dùng di động đến từ web có thể truy cập các tính năng trả phí, chỉ cần gọi `Adapty.activate()` hoặc `Adapty.identify()` với cùng `customer_user_id` bạn đã cung cấp ở bước trước (xem
2. Đặt tên cho key và thiết lập ngày hết hạn. Để API key hoạt động với Adapty, bạn cần cấp quyền **Read** cho tất cả các đối tượng. Nhấp **Save**.
3. Nhấp **Copy key**.
4. Trong Adapty, vào [App Settings → Paddle](https://app.adapty.io/settings/paddle) và dán key vào phần **Paddle API key**.
:::warning
Nếu bạn đặt ngày hết hạn cho Paddle API key, bạn phải tự tạo key mới và cập nhật trong Adapty trước khi key hết hạn. Tích hợp sẽ ngừng hoạt động mà không có cảnh báo khi key hết hạn, và người dùng sẽ không thể thực hiện giao dịch mua.
:::
### 1.2. Thêm các sự kiện sẽ được gửi đến Adapty \{#12-add-events-that-will-be-sent-to-adapty\}
1. Sao chép **Webhook URL** từ trang **Paddle** tương ứng trong Adapty.
2. Trong Paddle, vào [**Developer Tools → Notifications**](https://vendors.paddle.com/notifications-v2) và nhấp **New destination** để thêm webhook.
3. Nhập tên mô tả cho webhook. Chúng tôi khuyến nghị đặt tên có chứa "Adapty" để dễ tìm khi cần.
4. Dán **Webhook URL** từ Adapty vào trường **URL**. Đảm bảo bạn đang dùng webhook đúng với môi trường.
5. Đặt **Notification type** thành **Webhook**.
6. Chọn các sự kiện sau:
- `subscription.created`
- `subscription.updated`
- `transaction.created`
- `transaction.updated`
- `adjustment.created`
- `adjustment.updated`
7. Nhấp **Save destination** để hoàn tất thiết lập webhook.
### 1.3. Lấy và thêm secret key của webhook \{#13-retrieve-and-add-the-webhook-secret-key\}
1. Trong cửa sổ **Notifications**, nhấp vào ba dấu chấm bên cạnh webhook vừa tạo và chọn **Edit destination**.
2. Một trường mới tên **Secret key** sẽ xuất hiện trong bảng **Edit destination**. Sao chép nó.
3. Trong Adapty, vào [App Settings → Paddle](https://app.adapty.io/settings/paddle) và dán key vào trường **Notification secret key**. Key này được dùng để xác minh dữ liệu webhook trong Adapty.
### 1.4. Liên kết khách hàng Paddle với hồ sơ người dùng Adapty \{#14-match-paddle-customers-with-adapty-profiles\}
Adapty cần liên kết mỗi giao dịch mua với một [hồ sơ người dùng](profiles-crm) để có thể sử dụng trong ứng dụng. Theo mặc định, hồ sơ người dùng được tạo tự động khi Adapty nhận webhook từ Paddle. Bạn có thể chọn giá trị nào sẽ được dùng làm `customer_user_id` trong Adapty:
1. **Mặc định và khuyến nghị:** `customer_user_id` bạn truyền vào trường `custom_data` (xem [tài liệu Paddle](https://developer.paddle.com/build/transactions/custom-data))
2. `email` từ đối tượng Paddle Customer (xem [tài liệu Paddle](https://developer.paddle.com/paddle-js/methods/paddle-checkout-open/#parameters))
3. Paddle Customer ID theo định dạng `ctm-...` (xem [tài liệu Paddle](https://developer.paddle.com/paddle-js/methods/paddle-checkout-open/#parameters))
4. Không tạo hồ sơ người dùng. Chọn tùy chọn này nếu bạn muốn kiểm soát hồ sơ người dùng nhiều hơn và tự xử lý.
Bạn có thể cấu hình giá trị nào sẽ được sử dụng trong trường **Profile creation behavior** tại [App Settings → Paddle](https://app.adapty.io/settings/paddle).
## 2. Thêm sản phẩm Paddle vào Adapty \{#2-add-paddle-products-to-adapty\}
:::warning
Hãy nhớ thêm sản phẩm Paddle của bạn vào Adapty Dashboard hoặc thêm Paddle product ID vào các sản phẩm hiện có. Adapty chỉ theo dõi sự kiện cho các giao dịch gắn với những sản phẩm này. Nếu bỏ qua bước này, sự kiện giao dịch sẽ không được tạo.
:::
Paddle hoạt động trong Adapty giống như App Store và Google Play — đây là một nền tảng khác để bạn bán sản phẩm kỹ thuật số. Để cấu hình, hãy thêm các giá trị `product_id` và `price_id` tương ứng từ Paddle trong phần [Products](https://app.adapty.io/products) trong Adapty.
Trong Paddle, product ID có dạng `pro_...` và price ID có dạng `pri_...`. Bạn có thể tìm thấy chúng trong [danh mục sản phẩm Paddle](https://vendors.paddle.com/products-v2) khi mở một sản phẩm cụ thể:
Sau khi đã thêm sản phẩm, bước tiếp theo là đảm bảo Adapty có thể liên kết giao dịch mua với đúng người dùng.
## 3\. Cấp quyền truy cập cho người dùng trên thiết bị di động \{#3-provide-access-to-users-on-the-mobile\}
Để đảm bảo người dùng mua hàng trên web có thể truy cập trên thiết bị di động, hãy gọi `Adapty.activate()` hoặc `Adapty.identify()` với cùng `customer_user_id` mà bạn đã truyền khi thực hiện giao dịch mua. Xem [Identifying users](identifying-users) để biết thêm chi tiết.
## 4\. Kiểm tra tích hợp \{#4-test-your-integration\}
Sau khi thiết lập xong, bạn có thể kiểm tra tích hợp. Các giao dịch thực hiện trong môi trường Test của Paddle sẽ xuất hiện dưới dạng **Test** trong Adapty. Các giao dịch từ môi trường Production sẽ xuất hiện dưới dạng **Production**.
Tích hợp của bạn đã hoàn tất. Người dùng có thể mua gói đăng ký trên website và tự động truy cập các tính năng cao cấp trong ứng dụng di động, trong khi bạn theo dõi toàn bộ analytics gói đăng ký từ Adapty Dashboard thống nhất.
## Những lưu ý quan trọng \{#important-considerations\}
- Trong analytics của Adapty, số tiền giao dịch bao gồm thuế và phí Paddle, khác với dashboard của Paddle nơi số tiền được hiển thị sau khi trừ thuế và phí. Điều này có nghĩa là các con số bạn thấy trong Adapty sẽ cao hơn so với dashboard Paddle của bạn.
- Không giống các cửa hàng khác, hoàn tiền trong Paddle chỉ ảnh hưởng đến giao dịch cụ thể được hoàn tiền và không tự động hủy gói đăng ký. Gói đăng ký sẽ tiếp tục hoạt động trừ khi được hủy rõ ràng.
- Bạn cũng có thể thêm `variation_id` vào trường `custom_data` để quy kết giao dịch mua cho các phiên bản paywall cụ thể. Adapty sẽ xử lý dữ liệu này từ webhook và đưa vào analytics.
### Dùng thử có tính phí \{#paid-trials\}
Khi làm việc với dùng thử có tính phí trong Paddle, bạn cần tạo hai sản phẩm trong Adapty:
1. Tạo một sản phẩm mua một lần và liên kết với price Paddle tính phí cho giai đoạn dùng thử.
2. Sau đó tạo một sản phẩm gói đăng ký (Hàng tháng/Hàng tuần/v.v.) và liên kết với price Paddle có thành phần dùng thử miễn phí.
Từ góc độ Paddle, đây là một sản phẩm với hai price trong một giao dịch — một price cho khoản phí dùng thử (ví dụ: $0,99) và một price cho giai đoạn dùng thử miễn phí ($0,00).
Từ góc độ Adapty, điều này tạo ra hai sự kiện riêng biệt: một giao dịch mua một lần cho khoản thanh toán dùng thử và một sự kiện bắt đầu dùng thử cho sản phẩm gói đăng ký.
Ví dụ: khi người dùng bắt đầu dùng thử có tính phí $0,99 cho gói đăng ký $9,99/tháng, Paddle tạo một giao dịch với cả hai price, trong khi Adapty xử lý đây là một giao dịch mua một lần $0,99 (thanh toán ngay) và một sự kiện bắt đầu dùng thử $0,00 (gói đăng ký trong tương lai $9,99/tháng).
:::note
Khi người dùng hủy dùng thử có tính phí, bạn nhận được các sự kiện **Trial expired** và **Trial renewal canceled**.
:::
## Khai thác tối đa dữ liệu Paddle \{#get-more-from-your-paddle-data\}
:::important
Để các sự kiện Paddle hoạt động với các tích hợp, người dùng của bạn phải đăng nhập vào ứng dụng bằng tài khoản App Store/Google Play ít nhất một lần.
:::
Sau khi tích hợp với Paddle, Adapty sẵn sàng cung cấp thông tin phân tích ngay lập tức. Để tận dụng tối đa dữ liệu Paddle, bạn có thể thiết lập thêm các tích hợp Adapty để chuyển tiếp sự kiện Paddle — tập hợp toàn bộ analytics gói đăng ký vào một Adapty Dashboard duy nhất.
Các tích hợp bạn có thể dùng để chuyển tiếp và phân tích sự kiện Paddle:
- [AppsFlyer](appsflyer)
- [Webhook](webhook)
- [Posthog](posthog)
## Các hạn chế hiện tại \{#current-limitations\}
- **Hủy gói đăng ký**: Paddle có hai tùy chọn hủy gói đăng ký:
1. Hủy ngay lập tức: Gói đăng ký bị hủy ngay.
2. Hủy vào cuối kỳ: Gói đăng ký hủy vào cuối chu kỳ thanh toán hiện tại (tương tự gói đăng ký trong ứng dụng trên các cửa hàng ứng dụng).
- **Hoàn tiền**: Adapty theo dõi hoàn tiền toàn phần và một phần.
- **Thời gian ân hạn**: Theo mặc định, Paddle áp dụng thời gian ân hạn cố định 30 ngày cho các sự cố thanh toán, trong thời gian đó gói đăng ký vẫn còn hoạt động. Bạn có thể [tùy chỉnh thời gian ân hạn và hành động sau khi kết thúc (tạm dừng hoặc hủy gói đăng ký)](https://developer.paddle.com/build/retain/configure-payment-recovery-dunning#prerequisites).
**Dùng thử**: Nếu việc thu tiền thất bại sau khi dùng thử kết thúc, trạng thái gói đăng ký sẽ chuyển sang `past_due`. Trong môi trường production, Retain của Paddle áp dụng cửa sổ dunning để thử lại thanh toán trước khi hủy hoặc tạm dừng gói đăng ký. Trong sandbox, Retain không khả dụng, do đó không có lần thử lại thanh toán nào được thực hiện và gói đăng ký sẽ ở trạng thái `past_due` vô thời hạn.
---
**Xem thêm:**
- [Xác thực giao dịch mua trong Paddle, lấy mức độ truy cập và import lịch sử giao dịch từ Paddle bằng server-side API](api-adapty/operations/validatePaddlePurchase)
---
# File: custom-store
---
---
title: "Tích hợp ban đầu với các cửa hàng khác"
description: "Tích hợp ban đầu của Adapty với App Store: Hướng dẫn nhanh"
---
Chào mừng bạn đến với Adapty! Chúng tôi luôn ưu tiên giúp bạn bắt đầu nhanh chóng và đạt được kết quả tốt nhất cho ứng dụng của mình.
Việc tích hợp ban đầu chỉ cần thiết với [App Store](initial_ios), [Google Play](initial-android), [Stripe](stripe) và [Paddle](paddle) vì Adapty xác minh ứng dụng, sản phẩm và ưu đãi của bạn với các cửa hàng này.
Adapty không xác thực dữ liệu với các cửa hàng ứng dụng khác và không xử lý các giao dịch mua hàng thực hiện qua chúng. Tuy nhiên, bạn vẫn có thể đánh dấu các sản phẩm được bán qua các cửa hàng khác để Adapty cấp quyền truy cập nội dung có phí sau khi mua thành công, phản ánh giao dịch trong analytics và chia sẻ qua các tích hợp.
:::important Hãy đảm bảo backend của bạn xử lý giao dịch mua hàng và gửi transaction đến Adapty thông qua [Adapty server-side API](getting-started-with-server-side-api). Adapty chỉ cấp quyền truy cập, kích hoạt sự kiện giao dịch, gửi đến các tích hợp và phản ánh trong analytics sau khi nhận được transaction. ::: Để đánh dấu một sản phẩm là được bán qua cửa hàng ứng dụng tùy chỉnh, hãy chọn cửa hàng ứng dụng khi tạo sản phẩm. Nếu cửa hàng bạn cần chưa có trong danh sách, đây là cách tạo mới: 1. Trên trang **Products**, mở sản phẩm bạn muốn bán qua cửa hàng ứng dụng tùy chỉnh. 2. Chọn cửa hàng ứng dụng bạn muốn bán qua. Nếu chưa có trong danh sách, nhấp vào nút **Create Custom Store**.
3. Nhập **Title** và **Store ID** của cửa hàng.
4. Nhấp vào nút **Create store**.
Nếu backend của bạn được thiết lập đúng, Adapty sẽ nhận các giao dịch sản phẩm từ cửa hàng tùy chỉnh này, phản ánh chúng trong analytics, **Event Feed**, và [các tích hợp](https://app.adapty.io/integrations), đồng thời cấp quyền truy cập tương ứng.
## Khai thác tối đa dữ liệu từ cửa hàng tùy chỉnh \{#get-more-from-your-custom-store-data\}
:::important
Để các sự kiện từ cửa hàng tùy chỉnh của bạn hoạt động với các tích hợp, người dùng phải đăng nhập vào ứng dụng bằng tài khoản App Store/Google Play của họ ít nhất một lần.
:::
Sau khi thiết lập tích hợp cửa hàng tùy chỉnh, Adapty sẵn sàng cung cấp thông tin chi tiết ngay lập tức. Để tận dụng tối đa dữ liệu của bạn, bạn có thể thiết lập thêm các tích hợp Adapty để chuyển tiếp các sự kiện từ cửa hàng tùy chỉnh — tập hợp toàn bộ analytics gói đăng ký vào một Adapty Dashboard duy nhất.
Các tích hợp bạn có thể sử dụng để chuyển tiếp và phân tích sự kiện từ cửa hàng tùy chỉnh:
- [AppsFlyer](appsflyer)
- [Webhook](webhook)
- [Posthog](posthog)
---
# File: transfer-apps
---
---
title: "Chuyển ứng dụng sang tài khoản khác"
description: "Thay đổi chủ sở hữu ứng dụng trong Adapty"
---
Chuyển ứng dụng sang chủ sở hữu khác khi công ty bạn được mua lại, bạn đang bán ứng dụng, hoặc tái cơ cấu các thực thể kinh doanh. Quá trình chuyển giao liên quan đến việc phối hợp thay đổi trong Adapty, App Store Connect và Google Play Console để đảm bảo dịch vụ không bị gián đoạn.
## Chuyển quyền sở hữu ứng dụng \{#transfer-app-ownership\}
Hoàn tất việc chuyển giao trên cửa hàng trước, sau đó mới chuyển ứng dụng trong Adapty. Thứ tự này đảm bảo các giao dịch mua vẫn hoạt động xuyên suốt quá trình chuyển giao.
:::note
Không xóa hoặc tạo lại sản phẩm trong quá trình chuyển giao. Không thay đổi ID sản phẩm cho đến khi xác nhận việc chuyển giao đã hoàn tất.
:::
### Chuyển giao App Store (iOS) \{#app-store-ios-transfer\}
:::important
Khóa API App Store Connect (Issuer ID, Key ID, file .p8) được gắn với tài khoản, không phải với ứng dụng. Sau khi chuyển giao, bạn phải tạo khóa API mới từ tài khoản của chủ sở hữu mới và cập nhật chúng trong Adapty.
App-specific shared secret vẫn tiếp tục xác thực receipt trong thời gian chuyển giao, nhưng chủ sở hữu mới cũng phải tạo lại và cập nhật nó trong Adapty sau khi hoàn tất chuyển giao.
:::
1. **Chủ sở hữu mới:** Tạo tài khoản Adapty tại [app.adapty.io](https://app.adapty.io) nếu bạn chưa có.
2. **Chủ sở hữu cũ:** Khởi tạo quá trình chuyển giao ứng dụng trong App Store Connect theo [hướng dẫn chuyển giao](https://developer.apple.com/help/app-store-connect/transfer-an-app/overview-of-app-transfer) của Apple.
3. **Chủ sở hữu mới:** Chấp nhận yêu cầu chuyển giao trong App Store Connect.
4. **Chủ sở hữu cũ:** Gửi email đến [support@adapty.io](mailto:support@adapty.io) để chuyển ứng dụng trong Adapty. Cung cấp tên ứng dụng và địa chỉ email của chủ sở hữu mới.
5. **Chủ sở hữu mới:** Sau khi nhận ứng dụng trong Adapty, hoàn tất [hướng dẫn tích hợp App Store](initial_ios) để tạo và cấu hình tất cả thông tin xác thực dưới tài khoản của bạn.
### Chuyển giao Google Play (Android) \{#google-play-android-transfer\}
1. **Chủ sở hữu mới:** Tạo tài khoản Adapty tại [app.adapty.io](https://app.adapty.io) nếu bạn chưa có.
2. **Cả hai chủ sở hữu:** Đảm bảo cả hai tài khoản Google Play Developer đều đã đăng ký đầy đủ.
3. **Chủ sở hữu cũ:** Gửi yêu cầu chuyển giao qua Google Play Console hoặc Google Play Developer Support. Google có thể yêu cầu thêm tài liệu như số DUNS, hợp đồng hoặc bằng chứng bán hàng.
4. **Chủ sở hữu mới:** Xem xét và phê duyệt yêu cầu chuyển giao.
5. **Google:** Nhóm hỗ trợ của Google xử lý việc chuyển giao, thường trong vài ngày làm việc, nhưng có thể lâu hơn tùy thuộc vào xác minh tài khoản, độ phức tạp của gói đăng ký và thiết lập thanh toán.
6. **Chủ sở hữu cũ:** Sau khi Google hoàn tất chuyển giao, gửi email đến [support@adapty.io](mailto:support@adapty.io) để chuyển ứng dụng trong Adapty. Cung cấp tên ứng dụng và địa chỉ email của chủ sở hữu mới.
7. **Chủ sở hữu mới:** Sau khi nhận ứng dụng trong Adapty, hoàn tất [hướng dẫn tích hợp Google Play](initial-android) để tạo và cấu hình tất cả thông tin xác thực dưới tài khoản của bạn.
Quá trình chuyển giao bao gồm người dùng, gói đăng ký, thống kê, xếp hạng và thông tin trang cửa hàng. Tính liên tục thanh toán được duy trì cho các thuê bao hiện tại, nhưng các khoản thanh toán sẽ chuyển sang tài khoản merchant của chủ sở hữu mới chỉ sau khi hoàn tất chuyển giao. Báo cáo thanh toán và đơn hàng trước khi chuyển giao vẫn nằm trong tài khoản gốc. Theo dõi [hướng dẫn chuyển giao](https://support.google.com/googleplay/android-developer/answer/6230247) của Google để biết các yêu cầu chi tiết.
## Giảm thiểu rủi ro và thời điểm thực hiện \{#risk-mitigation-and-timing\}
**Những gì vẫn hoạt động trong quá trình chuyển giao:**
- Giao dịch mua và gia hạn (app-specific shared secret vẫn tiếp tục xác thực receipt trong thời gian chuyển giao)
- Quyền truy cập của các thuê bao hiện tại
- SDK vẫn hoạt động bình thường
**Những gì tạm thời ngừng hoạt động:**
- Các lệnh gọi API App Store Connect (cho đến khi cấu hình khóa mới)
- Thông báo từ server (cho đến khi cấu hình lại endpoint)
- Analytics có thể bị gián đoạn trong quá trình chuyển đổi thông tin xác thực
**Thời điểm khuyến nghị:**
- Thực hiện chuyển giao trong các khung giờ ít truy cập (3 giờ sáng - 6 giờ sáng theo múi giờ chính của người dùng)
- Chủ sở hữu mới sẵn sàng cấu hình thông tin xác thực ngay sau khi chấp nhận chuyển giao trên cửa hàng
- Dự trù 15-30 phút giữa thời điểm chấp nhận chuyển giao và hoàn tất tích hợp Adapty
**Sau khi hoàn tất chuyển giao:**
- Kiểm tra xác thực receipt ngay lập tức
- Theo dõi tỷ lệ gia hạn tự động trong 48 giờ
- Xác minh thông báo từ server đang đến được hệ thống của bạn
- Kiểm tra xem các giao dịch mua mới có đang được theo dõi chính xác không
## Xác minh chuyển giao đã hoàn tất thành công \{#verify-transfer-completed-successfully\}
Sau khi hoàn tất cả việc chuyển giao trong Adapty và trên cửa hàng:
1. **Kiểm tra quyền truy cập Dashboard**: Chủ sở hữu mới sẽ thấy ứng dụng trong Adapty Dashboard của họ.
2. **Xác minh kết nối khóa API**: Kiểm tra xem App Store Connect API Key mới hoặc service account Google Play có kết nối thành công trong Adapty không.
3. **Kiểm tra kết nối SDK**: Chạy ứng dụng của bạn và xác minh SDK Adapty khởi tạo mà không có lỗi.
---
# File: installation-of-adapty-sdks
---
---
title: "Cài đặt Adapty SDK"
description: "Cài đặt Adapty SDK cho iOS, Android và các ứng dụng đa nền tảng."
---
Bạn có ba cách để bắt đầu tùy theo sở thích:
- **Theo dõi hướng dẫn quickstart theo từng nền tảng**: Các hướng dẫn chứa các đoạn code sẵn sàng cho môi trường production, nên việc triển khai không mất nhiều thời gian.
- [iOS](ios-sdk-overview)
- [Android](android-sdk-overview)
- [React Native](react-native-sdk-overview)
- [Flutter](flutter-sdk-overview)
- [Unity](unity-sdk-overview)
- [Kotlin Multiplatform](kmp-sdk-overview)
- [Capacitor](capacitor-sdk-overview)
- **Dùng LLM**: Tài liệu của chúng tôi thân thiện với LLM. Đọc [hướng dẫn](adapty-cursor) của chúng tôi để tận dụng tối đa LLM với tài liệu Adapty.
- **Khám phá các ứng dụng mẫu**:
- [iOS (Swift)](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples)
- [Android (Kotlin)](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app)
- [React Native (Ví dụ cơ bản trên pure RN)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/BasicExample)
- [React Native (Ví dụ nâng cao – hữu ích cho việc phát triển, cho phép bạn làm việc với các trường hợp phức tạp hơn)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/AdaptyDevtools)
- [React Native (Expo dev build)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo)
- [React Native (Expo Go & Web)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock)
- [Flutter (Dart)](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example)
- [Unity (C#)](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets)
- [Kotlin Multiplatform](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example)
- [Capacitor](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples)
---
# File: sample-apps
---
---
title: "Ứng dụng mẫu"
description: ""
---
Để giúp bạn bắt đầu với Adapty SDK, chúng tôi đã chuẩn bị các ứng dụng mẫu minh họa cách tích hợp và sử dụng các tính năng chính. Những ứng dụng này cung cấp sẵn các triển khai về paywall, mua hàng và theo dõi analytics.
## Tại sao nên dùng ứng dụng mẫu? \{#why-use-sample-apps\}
- **Tích hợp nhanh:** Xem cách Adapty SDK hoạt động trong một ứng dụng thực tế.
- **Thực hành tốt nhất:** Làm theo các mô hình triển khai được khuyến nghị.
- **Gỡ lỗi & Kiểm thử:** Dùng ứng dụng mẫu để khắc phục sự cố và thử nghiệm trước khi tích hợp Adapty vào dự án của bạn.
## Các ứng dụng mẫu hiện có \{#available-sample-apps\}
- [iOS (Swift)](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples)
- [Android (Kotlin)](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app)
- [React Native (Ví dụ cơ bản trên RN thuần)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/BasicExample)
- [React Native (Ví dụ nâng cao – hữu ích cho việc phát triển, cho phép làm việc với các trường hợp phức tạp hơn)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/AdaptyDevtools)
- [React Native (Expo dev build)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo)
- [React Native (Expo Go & Web)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock)
- [Flutter (Dart)](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example)
- [Unity (C#)](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets)
- [Kotlin Multiplatform](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example)
- [Capacitor (React)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-react-example)
- [Capacitor (Vue.js)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-vue-example)
- [Capacitor (Angular)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-angular-example)
- [Capacitor (Công cụ phát triển nâng cao)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/adapty-devtools)
---
# File: builder-ui
---
---
title: "Giao diện Flow Builder"
description: "Tổng quan về giao diện và không gian làm việc của Flow Builder."
---
Giao diện Flow Builder chính bao gồm tất cả các công cụ cần thiết để thêm các thành phần hiển thị, chỉnh sửa thuộc tính của chúng và thay đổi logic của flow người dùng. Bài viết này giới thiệu từng khu vực của giao diện: chức năng và vị trí của từng thành phần.
## Điều khiển dự án và phím tắt hữu ích (thanh công cụ trên cùng) \{#project-controls-and-useful-shortcuts-top-toolbar\}
* **Close** Close: Thoát khỏi trình chỉnh sửa flow và quay lại trang danh sách flow.
* **App name** App: Hiển thị tên ứng dụng mà flow thuộc về.
* **All flows** Flows: Mở danh sách tất cả các flow của ứng dụng này.
* **Rename the flow**: Nhấn vào biểu tượng bút chì Pencil bên cạnh tên flow để đổi tên.
* **View mode toggle**: Chuyển đổi giữa chế độ xem thiết kế Cursor và [chế độ xem Remote Config](customize-flow-with-remote-config)Remote Config.
* **Undo/Redo**: Nhấn vào các biểu tượng mũi tên để hoàn tác Undo hoặc làm lại Redo các thay đổi trong flow. Bạn cũng có thể dùng ⌘Z / Ctrl+Z để hoàn tác.
* **Save draft / Publish**: Nhấn **Save draft** để lưu tiến trình mà chưa xuất bản (⌘ / Ctrl+S). Mở menu thả xuống Open dropdown để truy cập nút [**Publish**](builder-save-publish). Bạn chỉ có thể thêm flow vào một [placement](create-placement) sau khi đã xuất bản.
## Khu vực xem trước (ở giữa) \{#preview-area-center\}
Khu vực trung tâm của không gian làm việc mô phỏng giao diện flow trên thiết bị di động.
* Để chọn một thành phần và chỉnh sửa thuộc tính, nhấn vào nó. Để chọn một thành phần con bên trong một container, nhấn vào container trước, rồi nhấn vào thành phần con.
* Để chỉnh sửa thuộc tính của chính màn hình đó, nhấn vào vùng trống bên ngoài tất cả các thành phần, hoặc chọn màn hình trong bảng Screens and Layers.
* Để thay đổi thứ tự của một thành phần, kéo mục tương ứng lên hoặc xuống trong bảng Screens and Layers.
:::warning
Trình chỉnh sửa flow được thiết kế để tạo bố cục responsive. Do đó, bạn **không thể thay đổi vị trí các thành phần thủ công** — bạn chỉ có thể thay đổi thứ tự của chúng. Cài đặt bố cục của mỗi container quyết định cách các thành phần bên trong được phân bố.
:::
### Thanh màn hình đang hoạt động (phía trên bản xem trước thiết bị) \{#active-screen-bar-above-the-device-preview\}
- **Screen name** — một pill hiển thị tên màn hình hiện tại.
- **Toggle animations** Toggle animations — bật hoặc tắt bản xem trước animation của các thành phần; animation chạy liên tục cho đến khi tắt. Chỉ hiển thị khi màn hình đang hoạt động có ít nhất một [animation](builder-styling#animation). Không ảnh hưởng đến việc hiển thị animation trên thiết bị thực.
- **Add element** Plus — mở [thư viện thành phần](builder-elements) ở màn hình hiện tại. Tương đương với nút **+** ở đầu bảng Screens and Layers — tiện dụng khi bảng đang thu gọn.
### Điều khiển chế độ xem (thanh công cụ dưới cùng) \{#view-controls-bottom-toolbar\}
Các công cụ trong thanh công cụ dưới cùng cho phép bạn điều chỉnh bản xem trước.
* **Device**: Chọn một trong các mẫu iPhone và Android có sẵn để thay đổi kích thước viewport và hình dạng thiết bị.
* **Screen orientation**: Chuyển đổi giữa chế độ dọc Portrait và ngang Landscape để xem trước flow theo các hướng khác nhau.
* **Color scheme**: Chuyển đổi giữa chế độ sáng Light mode và tối Dark mode để xem thiết kế của bạn thích nghi với các theme khác nhau như thế nào.
* **Locale**: Chọn một ngôn ngữ/vùng để xem trước flow với nội dung đã được bản địa hóa.
* **View options**: Bật hoặc tắt viền thiết bị và các đường hướng dẫn vùng an toàn.
## Thuộc tính màn hình và thành phần (bảng bên phải) \{#screen-and-element-properties-right-panel\}
### Cài đặt màn hình và bố cục \{#screen-settings-and-layout\}
:::link
Bài viết chính: [Screens and Layers](paywall-layout-and-products)
:::
Khi không có thành phần nào được chọn, bảng bên phải cho phép bạn điều chỉnh các thuộc tính của [màn hình flow](paywall-layout-and-products) đang hoạt động, bao gồm:
* Tương tác với UI hệ thống (ví dụ: thanh trạng thái có hiển thị hay không)
* Quy tắc bố cục tự động
* Nền (màu sắc, hình ảnh hoặc video)
* Kích thước padding
* Hành vi cuộn dọc
Nếu màn hình chứa một số thành phần nhất định, chẳng hạn như [quiz tương tác](onboarding-quizzes), danh sách này sẽ được mở rộng với các thuộc tính liên quan.
### Thuộc tính thành phần \{#element-properties\}
Khi bạn chọn một thành phần, bảng bên phải cho phép bạn thay đổi kiểu và thuộc tính tương tác của nó.
#### Thuộc tính thiết kế \{#design-properties\}
:::link
Tìm hiểu thêm: [Định vị thành phần](manage-paywall-ui-elements), [Tạo kiểu thành phần](builder-styling)
:::
Tab **Design** cho phép bạn cấu hình giao diện và bố cục của thành phần được chọn:
* **Visibility**: Hiển thị hoặc ẩn thành phần. Bật **Conditional** để đặt quy tắc xác định khi nào thành phần sẽ hiển thị.
* **Position**: Chọn giữa các kiểu định vị Relative, Absolute hoặc Fixed.
* **Content** (chỉ áp dụng cho thành phần văn bản): Chỉnh sửa nội dung văn bản của thành phần, chèn [biến](#variables) và quản lý bản địa hóa.
* **Typography** (chỉ áp dụng cho thành phần văn bản): Cấu hình font, độ đậm, kích thước, màu sắc, căn chỉnh, trang trí và cắt ngắn.
* **Spacing**: Đặt margin và padding của thành phần.
* **Effects**: Thêm đổ bóng ngoài, đổ bóng trong, làm mờ nền hoặc làm mờ layer.
* **Animation**: Thêm các hiệu ứng animation (ví dụ: Pulse) và cấu hình thời gian và cường độ của chúng.
* **Appearance**: Điều chỉnh độ mờ và góc xoay.
* **Layout**: Chọn hướng bố cục (dọc hoặc ngang) và xác định cách phân bố các thành phần con.
#### Thuộc tính tương tác \{#interactions-properties\}
:::link
Tìm hiểu thêm: [Actions](onboarding-actions), [Điều hướng và tương tác](onboarding-navigation-branching)
:::
Tab **Interactions** cho phép bạn xác định điều gì xảy ra khi người dùng tương tác với thành phần được chọn. Mỗi tương tác bao gồm một **trigger** và một hoặc nhiều **action**:
* **Trigger** xác định *khi nào* điều gì đó xảy ra — ví dụ: **On Tap** (người dùng nhấn vào thành phần).
* **Action** xác định *điều gì* xảy ra — ví dụ: điều hướng đến màn hình khác hoặc thay đổi giá trị của một biến. Thêm nhiều action vào một trigger để thực thi chúng theo thứ tự.
Bạn có thể thêm nhiều trigger vào cùng một thành phần để thực thi nhiều action theo thứ tự.
## Bảng bên trái \{#left-panel\}
Bảng bên trái thay đổi chức năng dựa trên nút nào đang được chọn. Bạn có thể chọn giữa:
* [Screens and layers](#screens-and-layers)
* [Add element](#element-selection)
* [Products](#products)
* [Styles](#saved-styles)
* [Variables](#variables)
* [Localization](#localization)
### Screens and Layers \{#screens-and-layers\}
:::link
Bài viết chính: [Screens and Layers](paywall-layout-and-products)
:::
Nút layers Layers mở Screens and Layers (được hiển thị mặc định khi bạn mở flow builder).
Nó hiển thị từng màn hình dưới dạng cây các layer. Mỗi thành phần trên một màn hình là một layer, và các container có các thành phần con lồng bên trong. Bạn có thể kéo và thả các layer để sắp xếp lại chúng.
### Chọn thành phần \{#element-selection\}
:::link
Bài viết chính: [Elements](builder-elements)
:::
Nếu bạn nhấn vào nút plus Plus, bảng bên trái hiển thị danh sách các thành phần UI có sẵn và các biến thể của chúng. Nhấn vào một mục để thêm nó vào màn hình hiện tại dưới dạng một layer mới.
### Products \{#products\}
:::link
Bài viết chính: [Products](paywall-product-block)
:::
Nút products Products mở danh sách sản phẩm. Nó hiển thị các sản phẩm nào được gán cho từng màn hình trong flow của bạn.
Danh sách này chỉ để xem. Để gán sản phẩm cho một màn hình, hãy thêm thành phần Product và cấu hình nó trong bảng bên phải. Để tạo hoặc chỉnh sửa sản phẩm, hãy sử dụng trang **Products** trên Adapty Dashboard.
### Saved styles \{#saved-styles\}
:::info
Tìm hiểu thêm:
- [Tạo kiểu thành phần](builder-styling)
- [Nội dung văn bản](onboarding-text)
- [Chế độ tối](paywall-dark-mode)
:::
Nút styles Styles mở Saved Styles.
Tại đây, bạn có thể chỉnh sửa và quản lý các kiểu toàn cục. Nếu nhiều thành phần trong flow của bạn sử dụng cùng một kiểu chữ hoặc màu sắc, hãy lưu dữ liệu này dưới dạng kiểu toàn cục. Sau đó bạn có thể tái sử dụng nó chỉ với một cú nhấp.
Hiện tại, Flow Builder hỗ trợ hai loại kiểu toàn cục — Font styles và Color styles. Mỗi Color style có thể tùy chọn có giá trị riêng cho chế độ tối.
### Variables \{#variables\}
:::link
Bài viết chính: [Variables](onboarding-variables)
:::
Nút brackets Variables mở Variables.
Tại đây, bạn có thể tạo và quản lý các biến cho flow của mình. Khi chạy, SDK thay thế các placeholder biến bằng giá trị thực — thuộc tính người dùng, giá sản phẩm, chuỗi đã được bản địa hóa và nhiều hơn nữa.
Các biến được nhóm thành hai tab:
* **Custom**: Các biến bạn tạo và kiểm soát thông qua các action.
* **Elements**: Các giá trị được xác định bởi tương tác của người dùng — chẳng hạn như câu trả lời quiz, trạng thái toggle hoặc lựa chọn tab.
Biến sản phẩm — giá, tên và các dữ liệu sản phẩm khác — không được liệt kê trong bảng này. Tham chiếu trực tiếp đến chúng khi chỉnh sửa thành phần văn bản.
Sử dụng biến để:
* **Bind text**: Hiển thị nội dung động thay vì chuỗi tĩnh.
* **Control visibility**: Hiển thị hoặc ẩn các thành phần dựa trên điều kiện (ví dụ: ẩn nút nâng cấp cho người dùng premium).
* **Interact with the user**: Truy cập dữ liệu từ các trường nhập liệu của người dùng, chẳng hạn như form hoặc quiz.
### Localization \{#localization\}
:::link
Bài viết chính: [Localization](add-paywall-locale-in-adapty-paywall-builder)
:::
Chế độ xem Localization cho phép bạn quản lý tất cả nội dung có thể dịch trong flow của mình. Nó hiển thị một bảng gồm tất cả các chuỗi văn bản và hình ảnh, được tổ chức theo màn hình, với các cột cho từng ngôn ngữ/vùng. Từ chế độ xem này, bạn có thể:
* Thêm ngôn ngữ/vùng mới và chỉnh sửa các chuỗi đã dịch trực tiếp.
* Theo dõi trạng thái dịch — mỗi hàng được đánh dấu là **Done** hoặc **Missing**.
* Lọc theo màn hình hoặc chỉ hiển thị các bản dịch còn thiếu.
* Sử dụng **AI Translate** để tự động dịch nội dung, hoặc **Import/Export** bản dịch theo lô.
---
# File: flow-builder-recipes
---
---
title: "Common flow recipes"
description: "Step-by-step guides for building common screen templates in the Flow Builder."
---
This section walks through how to build the most common screen templates in the Flow Builder — element by element, from the layout choices to the interactions. Each guide is self-contained and uses the standard Flow Builder elements.
3. Các nút mua hàng, liên kết và nút đóng đã được cấu hình sẵn hành động. Với liên kết, hãy [cấu hình URL để điều hướng người dùng](#links). Với các loại nút khác, vào bảng **Interactions**. Tại đó, trong phần **Button triggers**, thiết lập các [hành động](onboarding-actions) mà nút cần thực hiện.
4. Cấu hình [thiết kế nút](manage-paywall-ui-elements) trong bảng **Design**.
## Các loại nút \{#button-types\}
### Nút mua hàng \{#purchase-buttons\}
:::link
Để các nút mua hàng hoạt động, hãy gắn sản phẩm vào màn hình và thêm phần tử **Products**. Xem [hướng dẫn](paywall-product-block).
:::
Nút mua hàng khởi động in-app purchase cho sản phẩm mà người dùng đang chọn trên màn hình. SDK xử lý giao dịch tự động, nên bạn không cần xử lý mua hàng trong code ứng dụng.
Để thêm nút mua hàng:
1. Nhấp **+** và chọn **Button**, sau đó chọn một preset nút.
2. Với nút đang được chọn, mở tab **Interactions** ở bảng bên phải.
3. Nhấp **Add trigger** > **On tap**, rồi nhấp **Add action**.
4. Đặt **Action** thành **Purchase** và **Product** thành `products.selectedProduct`. Biến `products.selectedProduct` luôn trỏ đến sản phẩm hiện đang được chọn trên màn hình.
:::tip
Bạn có thể thu hút thêm sự chú ý vào nút mua hàng bằng cách thêm hiệu ứng chuyển động. Paywall Builder hiện hỗ trợ kiểu animation **Pulse**.
Cấu hình kiểu animation trong bảng **Design**.
:::
### Liên kết \{#links\}
:::important
Các nút **Terms of Use** và **Privacy Policy** có sẵn hành động **Open URL**. Đặt URL đích tại đây. URL trống trong Open URL và [liên kết nội tuyến](onboarding-text#inline-link) sẽ chặn việc xem trước và xuất bản.
:::
Để tuân thủ một số yêu cầu của cửa hàng, bạn có thể thêm liên kết đến:
- Điều khoản dịch vụ
- Chính sách quyền riêng tư
- Khôi phục giao dịch
Để thêm liên kết:
1. Nhấp **+** và chọn **Button > Links**. Thao tác này sẽ thêm một hàng nút nội tuyến với các hành động được định sẵn: khôi phục giao dịch hoặc mở URL. Nếu bạn không cần tất cả các nút, hãy xóa những nút không cần thiết trong bảng layers.
2. Bây giờ, thiết lập các hành động cho nút:
- Nút **Restore purchases** đã xử lý việc khôi phục giao dịch sẵn rồi.
- Với mỗi liên kết còn lại:
1. Nhấp vào nút để chọn và chuyển sang tab **Interactions** ở bên phải.
2. Dán URL vào ô nhập liệu.
3. Mặc định, URL sẽ mở trong trình duyệt trong ứng dụng để trải nghiệm liền mạch. Nếu muốn điều hướng người dùng đến trình duyệt bên ngoài, hãy chọn hộp kiểm **Open in external browser**.
### Đóng flow \{#close-flow\}
Nút **Close** đóng flow tự động.
Để thêm nút đóng, nhấp **+** và chọn **Button > Close flow**.
:::tip
Dùng vị trí **Absolute** để đặt nút đóng ở góc màn hình.
:::
Bạn cũng có thể cấu hình bất kỳ nút nào khác để đóng flow bằng cách dùng [hành động](onboarding-actions).
### Nút tùy chỉnh \{#custom-buttons\}
Bất kỳ nút nào bạn thêm vào đều có thể được cấu hình để thực hiện các hành động khi nhấn:
- Điều hướng đến màn hình tiếp theo
- Hiển thị cảnh báo
- Đặt [biến](onboarding-variables)
- [Hiển thị hoặc ẩn các phần tử trên màn hình](onboarding-element-visibility)
- Mở URL
- Khôi phục giao dịch
- Thực hiện hành động có điều kiện
---
# File: builder-tabs
---
---
title: "Tabs"
description: "Thêm điều hướng tab để chuyển đổi các bảng nội dung trong một flow."
---
**Tabs** chia một phần màn hình thành các bảng nội dung có thể chuyển đổi — người dùng nhấn vào tiêu đề tab và bảng bên dưới cập nhật tương ứng.
{/* TODO: on-device GIF */}
## Thêm, xóa và chọn tab \{#add-remove-and-select-tabs\}
Mỗi tab gồm hai phần
- **Tab header** — nhãn có thể nhấn (Tab 1, Tab 2, v.v.).
- **Tab content** — một container cho mỗi tab. Bất cứ thứ gì bạn đặt trong container nội dung sẽ hiển thị khi tab đó được chọn.
Nhấn **Add tab** để thêm tab mới. Mỗi tab mới sẽ có một container nội dung tương ứng.
Để đặt một tab cụ thể là active khi màn hình xuất hiện lần đầu, bật **Selected by default**.
## Tùy chỉnh giao diện tab \{#style-the-tabs\}
### Templates \{#templates\}
Flow Builder cung cấp ba template tab có sẵn:
- **Segment control** — bộ chuyển đổi hình viên thuốc với góc bo tròn quanh tab đang được chọn.
- **Button Tabs** — các tab được tạo kiểu như nút bấm riêng biệt.
- **Underline** — nhãn văn bản có gạch chân đánh dấu tab đang được chọn.
### Trạng thái tab \{#tab-states\}
Mỗi tab riêng lẻ có bộ chuyển đổi trạng thái (**Default / Selected**) để tùy chỉnh trạng thái đang active và không active riêng biệt — kiểu chữ, màu sắc, nền và viền theo từng trạng thái.
## Selectable group \{#selectable-group\}
Tabs là một **selectable group chọn một** — đúng một tab được active tại một thời điểm. Quản lý nhóm từ bảng **Screen settings**, trong mục [Selectable groups](paywall-layout-and-products#selectable-groups).
Nhóm cung cấp hai biến:
- `tabs.selectedOptionId` — ID của tab đang được chọn. Dùng trong các điều kiện.
- `tabs.selectedOptionTitle` — nhãn của tab đang được chọn. Dùng trong văn bản động.
Thay `tabs` bằng **Group ID** tùy chỉnh của bạn nếu bạn đã đổi tên nhóm.
Xem [Selectable elements and groups](flow-selectable-elements) để có cái nhìn toàn diện hơn.
---
# File: builder-toggles
---
---
title: "Toggles"
description: "Thêm công tắc toggle vào luồng thanh toán của bạn."
---
:::warning
Apple có thể từ chối ứng dụng sử dụng toggle dùng thử được chọn sẵn. Một toggle được đặt mặc định ở trạng thái "bật" có thể bị gắn cờ là dark pattern thao túng người dùng theo App Store Review Guidelines — điều này ngụ ý người dùng đồng ý dùng thử miễn phí mà không cần lựa chọn rõ ràng.
Để tránh bị từ chối, hãy đặt toggle ở trạng thái **tắt** theo mặc định và để người dùng tự chọn tham gia dùng thử.
:::
Toggle dùng thử là một công tắc nhị phân cho phép người dùng lựa chọn giữa sản phẩm tiêu chuẩn và sản phẩm dùng thử trên một paywall. Khi người dùng thay đổi trạng thái của nó, toggle có thể kích hoạt một hành động — chẳng hạn như hoán đổi nhóm sản phẩm, cập nhật biến, hoặc hiển thị/ẩn các phần tử — ngay lập tức.
Để thêm toggle dùng thử, nhấn **+** trên màn hình đích và chọn **Trial toggle**.
Mỗi toggle dùng thử là một phần tử có thể chọn thuộc kiểu **Toggle**. Mỗi phần tử có thể chọn đều được gán một biến để phản ánh trạng thái của nó — ví dụ: một toggle có tên `trial` sẽ có biến `trial.is_selected` với giá trị `True` hoặc `False`.
Để các phần tử khác phụ thuộc vào trạng thái của toggle, hãy thiết lập [hành động](onboarding-actions) có điều kiện hoặc [hiển thị có điều kiện](onboarding-element-visibility) dựa trên biến này.
---
# File: builder-reviews-and-testimonials
---
---
title: "Đánh giá và nhận xét"
description: "Thêm đánh giá, xếp hạng và bằng chứng xã hội vào paywall."
---
Danh mục phần tử **User Engagement** cung cấp bốn template để hiển thị đánh giá, xếp hạng và bằng chứng xã hội trên paywall. Mỗi template là một bố cục có thể chỉnh sửa hoàn toàn — thay thế văn bản mẫu và áp dụng [màu sắc](builder-styling) cùng [kiểu chữ](onboarding-text) của bạn để phù hợp với phần còn lại của flow.
## Review \{#review\}
Một thẻ gồm xếp hạng, trích dẫn và tên tác giả. Dùng để nổi bật một câu trích dẫn đáng nhớ từ người dùng.
## Rating \{#rating\}
Số lượt đánh giá và hàng sao, ví dụ "17000+ rating". Dùng để nhấn mạnh số lượng đánh giá.
## App Rating \{#app-rating\}
Điểm nổi bật kèm số lượt đánh giá, ví dụ "4.9 / Based on 1000+ reviews". Dùng để làm nổi bật điểm tổng thể cao.
## Social Proof \{#social-proof\}
Nhóm avatar kèm số lượng thành viên, ví dụ "Join 50,000+ users". Dùng để nhấn mạnh quy mô cộng đồng.
---
# File: flow-timer
---
---
title: "Đồng hồ đếm ngược"
description: "Thêm đồng hồ đếm ngược vào paywall."
---
**Countdown timer** đếm ngược từ một khoảng thời gian cố định về không — khi về đến không, màn hình sẽ dừng lại.
## Mẫu \{#templates\}
Danh mục cung cấp bốn kiểu hiển thị:
- **Blocks** — Ngày, giờ, phút và giây trong các ô riêng biệt có nhãn.
- **Inline Units** — Văn bản một dòng kèm đơn vị theo sau.
- **Inline** — Chỉ hiển thị số.
- **Badge** — Hiển thị số dạng viên thuốc.
## Cài đặt \{#settings\}
### Đặt thời lượng \{#set-the-duration\}
Trong phần **Countdown** ở bảng bên phải, nhập thời lượng bắt đầu theo ngày, giờ, phút và giây.
### Cấu hình hành vi \{#configure-the-behavior\}
Dropdown **Behavior** kiểm soát thời điểm bắt đầu đếm ngược:
- **Every appear** — Khởi động lại mỗi khi người dùng mở màn hình. Mặc định.
- **First appear** — Bắt đầu khi người dùng xem màn hình lần đầu trong phiên ứng dụng hiện tại. Tiếp tục đếm nếu họ quay lại trong cùng phiên; đặt lại khi khởi động lại ứng dụng.
- **First appear (persisted)** — Bắt đầu khi người dùng mở màn hình lần đầu và tiếp tục đếm qua các lần khởi động ứng dụng.
### Kích hoạt hành động khi hết giờ \{#trigger-an-action-when-the-timer-ends\}
:::link
Bài viết chính: [Hành động](onboarding-actions)
:::
Thêm trigger **On timer end** để thực hiện một hành động khi đồng hồ đếm ngược về không — ví dụ: chuyển sang màn hình khác hoặc ẩn huy hiệu giảm giá.
---
# File: onboarding-quizzes
---
---
title: "Câu hỏi trắc nghiệm trong flow"
description: "Thêm câu hỏi trắc nghiệm tương tác vào flow Adapty của bạn để thu thập sở thích người dùng và tạo flow được cá nhân hóa — không cần viết code."
---
Dùng câu hỏi trắc nghiệm để hiển thị cho người dùng các lựa chọn được định sẵn. Khác với ô nhập liệu, câu hỏi trắc nghiệm không có trường nhập tay — người dùng chọn từ các tùy chọn bạn đã thiết lập. Dùng chúng để thu thập sở thích, phân khúc người dùng, hoặc phân nhánh flow dựa trên câu trả lời của họ.
### Thêm câu hỏi trắc nghiệm \{#add-a-quiz\}
1. Nhấp **+** ở góc trên bên trái.
2. Chọn **Quiz**.
3. Chọn loại câu hỏi trắc nghiệm:
- **Icon/image/emoji options:** Danh sách dọc các tùy chọn có thể chọn, mỗi tùy chọn có icon, hình ảnh hoặc emoji kèm nhãn văn bản.
- **Icon/image/emoji grid:** Lưới các tùy chọn có thể chọn, mỗi tùy chọn có icon, hình ảnh hoặc emoji.
- **Rating:** Thang điểm để người dùng đánh giá — theo số hoặc theo sao.
### Thiết lập điều hướng có điều kiện \{#set-up-conditional-navigation\}
Để định tuyến người dùng theo hướng khác nhau dựa trên lựa chọn của họ, hãy đặt hành động có điều kiện trên **nút điều hướng**, không phải trên tùy chọn câu hỏi:
1. Chọn nút điều hướng.
2. Trong bảng **Interactions**, thêm trigger **On Tap** với hành động **Conditional**.
3. Trong hộp thoại **Edit Action**, xây dựng hàng **if**:
- Ở bên trái, nhấp `{}` và chọn **Elements → Screen → `
2. Nhấp **Create product** ở góc trên bên phải. Adapty hỗ trợ tất cả các loại sản phẩm: gói đăng ký, non-consumable \(bao gồm cả lifetime\) và consumable.
3. Chọn **Create a new product and push to stores**.
4. Nhập các thông tin sau:
- **Product name**: nhập tên sản phẩm để sử dụng trong Adapty dashboard. Tên này chủ yếu để bạn tham khảo, vì vậy hãy chọn tên phù hợp nhất với bạn khi sử dụng trên Adapty Dashboard.
- **Access Level**: chọn [mức độ truy cập](access-level) mà sản phẩm thuộc về. Mức độ truy cập dùng để xác định các tính năng được mở khóa sau khi mua sản phẩm. Lưu ý danh sách này chỉ chứa các mức độ truy cập đã được tạo trước đó. Mức độ truy cập `premium` được tạo sẵn trong Adapty, nhưng bạn cũng có thể [thêm mức độ truy cập khác](access-level).
- **Subscription duration**: chọn thời hạn gói đăng ký từ danh sách.
- **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**: Thời hạn gói đăng ký.
- **Lifetime**: Dùng khi sản phẩm mở khóa tính năng premium của ứng dụng mãi mãi.
- **Non-Subscriptions**: Dành cho các sản phẩm không phải gói đăng ký và do đó không có thời hạn. Có thể dùng để mở khóa tính năng bổ sung, consumable, v.v.
- **Consumables**: Các mục consumable có thể mua nhiều lần và có thể được tiêu thụ trong quá trình sử dụng ứng dụng. Ví dụ như tiền tệ trong game và các vật phẩm thêm. Lưu ý rằng sản phẩm consumable không ảnh hưởng đến mức độ truy cập. Để cấp mức độ truy cập từ sản phẩm mua một lần, hãy dùng **Non-Subscriptions**.
- **Price (USD)**: Giá sản phẩm tính bằng USD. Giá này sẽ được dùng làm cơ sở để tự động tính và thiết lập giá cho tất cả các quốc gia. Bạn có thể [tùy chỉnh giá cho từng quốc gia và khu vực](edit-product#set-country-specific-prices) sau.
5. Nhấp **Save & Continue**.
6. Cấu hình thông tin sản phẩm cho App Store nếu bạn có kế hoạch xuất bản ở đó:
- **Product ID**: Tạo ID duy nhất và cố định cho sản phẩm.
- **Product group**: Chọn nhóm sản phẩm hiện có mà bạn đã tạo trong App Store Connect hoặc nhấp **Create new Product Group** và đặt tên cho nhóm đó. Sau khi Adapty tạo xong, bạn có thể chọn từ danh sách thả xuống.
- **Screenshot**: Tải lên ảnh chụp màn hình của in-app purchase thể hiện rõ sản phẩm hoặc dịch vụ được cung cấp. Ảnh chụp màn hình này chỉ dùng cho quá trình xét duyệt App Store và không được hiển thị trên App Store. Xem yêu cầu về kích thước và định dạng ảnh chụp màn hình [tại đây](https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications/).
7. Nhấp **Push data to App Store**.
:::warning
Nếu đây là sản phẩm đầu tiên của ứng dụng này, bạn phải gửi thủ công để xét duyệt trong App Store Connect. Điều này sẽ không cần thiết về sau. Sau khi xét duyệt hoàn tất, trạng thái sản phẩm trong Adapty sẽ tự động cập nhật.
:::
8. Cấu hình thông tin sản phẩm cho Google Play nếu bạn có kế hoạch xuất bản ở đó:
- **Base Product ID**: Tạo ID duy nhất và cố định cho sản phẩm.
- **Subscription**: Chọn nhóm gói đăng ký hiện có mà bạn đã tạo trong Google Play Console hoặc nhấp **Create new Product Group** và đặt tên và ID cho nhóm đó. Sau khi Adapty tạo xong, bạn có thể chọn từ danh sách thả xuống.
:::note
Grace Period và Account Hold Period sẽ được tự động đặt về giá trị mặc định theo quy định của Play Store. Bạn có thể thay đổi chúng sau trong Google Play Console.
:::
9. Nhấp **Push data to Play Store**.
10. Đối với iOS, cấu hình ưu đãi giới thiệu – dùng thử miễn phí – bằng cách chọn **Free duration** từ danh sách thả xuống. Trong thiết lập ban đầu này, bạn có thể thêm thời gian dùng thử miễn phí giới thiệu. Sau khi sản phẩm chính được các cửa hàng phê duyệt, bạn có thể [thêm các ưu đãi khác](offers) (ví dụ: ưu đãi, ưu đãi thu hút khách hàng cũ) bằng cách liên kết ID hiện có từ console của cửa hàng.
:::important
Ưu đãi giới thiệu không tự đồng bộ với Google Play. Không giống App Store, Google Play không có loại "introductory offer" riêng biệt — các ưu đãi dùng thử miễn phí và giảm giá đều được cấu hình dưới dạng **offers** trên base plan. [Tạo offer trong Google Play Console và liên kết với sản phẩm Adapty của bạn](google-play-offers).
:::
11. Cuối cùng, nhấp **Save** để xác nhận tạo sản phẩm.
## Tạo sản phẩm và kết nối sản phẩm đã có trong cửa hàng \{#create-product-and-connect-existing-store-products\}
:::warning
Trước khi bắt đầu, hãy đảm bảo bạn đã:
- Cấu hình tích hợp với các cửa hàng cần thiết:
- [App Store](initial_ios)
- [Google Play](initial-android)
- Tạo sản phẩm trong các cửa hàng cần thiết:
- [App Store](app-store-products)
- [Google Play](android-products)
**Nếu bạn chưa có sản phẩm nào**, hãy xem hướng dẫn [Đẩy lên cửa hàng](#create-product-and-push-to-store) để tạo chúng cùng lúc trong Adapty và các cửa hàng.
:::
2. Nhấp **Create product** ở góc trên bên phải. Adapty hỗ trợ tất cả các loại sản phẩm: gói đăng ký, non-consumable \(bao gồm cả lifetime\) và consumable.
3. Chọn **Connect an existing store product**.
4. Nhập các thông tin sau:
- **Product name**: nhập tên sản phẩm để sử dụng trong Adapty dashboard. Tên này chủ yếu để bạn tham khảo, vì vậy hãy chọn tên phù hợp nhất với bạn khi sử dụng trên Adapty Dashboard.
- **Access Level ID**: chọn [mức độ truy cập](access-level) mà sản phẩm thuộc về. Mức độ truy cập dùng để xác định các tính năng được mở khóa sau khi mua sản phẩm. Lưu ý danh sách này chỉ chứa các mức độ truy cập đã được tạo trước đó. Mức độ truy cập `premium` được tạo sẵn trong Adapty, nhưng bạn cũng có thể [thêm mức độ truy cập khác](access-level).
- **Subscription duration**: chọn thời hạn gói đăng ký từ danh sách.
- **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**: Thời hạn gói đăng ký.
- **Lifetime**: Dùng khi sản phẩm mở khóa tính năng premium của ứng dụng mãi mãi.
- **Non-Subscriptions**: Dành cho các sản phẩm không phải gói đăng ký và do đó không có thời hạn. Có thể dùng để mở khóa tính năng bổ sung, consumable, v.v.
- **Consumables**: Các mục consumable có thể mua nhiều lần và có thể được tiêu thụ trong quá trình sử dụng ứng dụng. Ví dụ như tiền tệ trong game và các vật phẩm thêm. Lưu ý rằng sản phẩm consumable không ảnh hưởng đến mức độ truy cập. Để cấp mức độ truy cập từ sản phẩm mua một lần, hãy dùng **Non-Subscriptions**.
- **Price (USD)**: Giá sản phẩm tính bằng USD. Nếu sản phẩm đã có trong cửa hàng, giá trị này sẽ không ảnh hưởng đến giá thực tế trong cửa hàng; bạn có thể chọn bất kỳ giá trị nào từ danh sách. Sau đó, bạn có thể [tùy chỉnh giá cho từng khu vực](edit-product#set-country-specific-prices) ngay trong Adapty dashboard.
5. Nhấp **Continue**.
6. Cấu hình thông tin sản phẩm từ mỗi cửa hàng:
- **App Store:**
- **App Store Product ID:** Mã định danh duy nhất này được dùng để truy cập sản phẩm trên thiết bị. Chọn từ danh sách. Nếu không thấy trong danh sách, hãy kiểm tra cấu hình trong App Store Connect và đảm bảo nó chính xác và thuộc ứng dụng này.
- **Play Store:**
- **Google Play Product ID:** Đây là mã định danh sản phẩm từ Play Store. Chọn từ danh sách. Nếu không thấy trong danh sách, hãy kiểm tra cấu hình trong Google Play Console và đảm bảo nó chính xác và thuộc ứng dụng này.
- **Base Plan ID:** ID này dùng để xác định base plan cho sản phẩm trên Play Store. Khi thêm Product ID của gói đăng ký trên Play Store, bạn cần cung cấp Base Plan ID. Một base plan xác định các chi tiết cơ bản của gói đăng ký, bao gồm chu kỳ thanh toán, loại gia hạn (tự động gia hạn hoặc trả trước) và giá tương ứng. Lưu ý rằng trong Adapty, mỗi sự kết hợp giữa cùng một gói đăng ký và các base plan khác nhau được coi là sản phẩm riêng biệt.
- **Legacy fallback product**: Sản phẩm dự phòng này chỉ được dùng cho các ứng dụng sử dụng phiên bản SDK Adapty cũ hơn (phiên bản 2.5 trở xuống). Bằng cách đánh dấu sản phẩm là tương thích ngược trong Google Play Console, Adapty có thể xác định liệu nó có thể được mua bởi các phiên bản SDK cũ hay không. Đối với trường này, hãy chỉ định giá trị theo định dạng `
## Thiết lập giá theo từng quốc gia \{#set-country-specific-prices\}
Bạn có thể thiết lập các mức giá khác nhau cho từng khu vực ngay trong Adapty Dashboard, và các mức giá theo quốc gia này sẽ tự động được áp dụng cho sản phẩm của bạn trong App Store Connect và/hoặc Google Play Console.
Để thiết lập giá theo từng quốc gia:
1. [Mở sản phẩm để chỉnh sửa](#edit-product).
2. Nhấp **Download** để xuất giá hiện tại từ các cửa hàng theo đúng định dạng hoặc tạo một file CSV mới.
3. Cập nhật giá trong file CSV. Tuân theo [định dạng](#csv-file-format). Nếu bạn để nguyên giá của một quốc gia hoặc không đưa vào file, sẽ không có thay đổi nào xảy ra. Khi bạn tải lên CSV, Adapty sẽ so sánh giá và chỉ cập nhật những giá có sự khác biệt.
4. Trong cửa sổ **Edit**, nhấp **Upload** và chọn file CSV.
5. Nếu bạn muốn các thay đổi cũng áp dụng cho người dùng đang đăng ký hiện tại, hãy chọn **Apply to existing subscribers**.
6. Xem lại các thay đổi sẽ được áp dụng và nhấp **Save changes**.
### Định dạng file CSV \{#csv-file-format\}
:::tip
Bạn có thể tái sử dụng cùng một file CSV nếu bạn có các sản phẩm tương tự trong một ứng dụng hoặc muốn thiết lập cùng mức giá cho các ứng dụng khác nhau.
:::
Cách dễ nhất để chỉnh sửa giá trong CSV là [tải xuống file với giá hiện tại và chỉnh sửa trực tiếp](#set-country-specific-prices).
Tuy nhiên, nếu bạn tự tạo file, file phải chứa các cột sau:
- `region_name`
- `region_code`
- `app_store_currency`
- `app_store_requested_price`
- `play_store_currency`
- `play_store_requested_price`
Ví dụ:
```
region_name,region_code,app_store_currency,app_store_requested_price,play_store_currency,play_store_requested_price
United States,US,,8.99,,8.99
United Arab Emirates,AE,USD,8.99,AED,39.99
Germany,DE,USD,8.99,USD,8.99
```
## Xem nhật ký kiểm tra \{#view-audit-log\}
Adapty ghi lại tất cả các thay đổi về giá cho từng sản phẩm, giúp bạn theo dõi ai đã thực hiện thay đổi và khi nào. Để xem nhật ký kiểm tra:
1. Vào **[Products](https://app.adapty.io/products)** từ menu chính của Adapty.
2. Nhấp vào biểu tượng ba chấm bên cạnh sản phẩm và chọn **Audit log**.
Bảng nhật ký kiểm tra hiển thị từng thay đổi về giá kèm theo ngày, tên và vai trò của thành viên trong nhóm, cùng số lượng thay đổi.
Để tải xuống bản chi tiết CSV của một sự kiện, nhấp vào biểu tượng tải xuống ở hàng tương ứng.
---
# File: delete-product
---
---
title: "Xóa sản phẩm"
description: "Tìm hiểu cách xóa sản phẩm gói đăng ký trong Adapty mà không làm gián đoạn doanh thu của ứng dụng."
---
Bạn chỉ có thể xóa các sản phẩm không được sử dụng trong bất kỳ paywall nào.
Để xóa sản phẩm:
1. Vào **[Products](https://app.adapty.io/products)** từ menu chính của Adapty.
2. Nhấn nút **3-dot** bên cạnh sản phẩm và chọn **Delete**.
2. Nhập tên sản phẩm bạn muốn xóa.
3. Nhấn **Delete forever**.
---
# File: add-product-to-paywall
---
---
title: "Thêm sản phẩm vào paywall"
description: "Tìm hiểu cách thêm và quản lý sản phẩm trên các paywall trong Adapty."
---
Để sản phẩm hiển thị và có thể chọn trong [paywall](paywalls) cho người dùng ứng dụng, hãy thực hiện các bước sau:
1. Trong khi [cấu hình paywall](create-paywall), nhấp vào **Add product** bên dưới tiêu đề **Products**.
2. Từ danh sách thả xuống, chọn các sản phẩm sẽ hiển thị cho khách hàng. Danh sách chỉ chứa các sản phẩm đã được tạo trước đó. Thứ tự sản phẩm được giữ nguyên ở phía SDK, vì vậy cần cân nhắc thứ tự mong muốn khi cấu hình paywall. Ngoài ra, bạn có thể chỉ định ưu đãi cho sản phẩm nếu muốn.
3. Nhấp vào **Create as draft** hoặc **Save and publish** tùy theo trạng thái của paywall.
Lưu ý rằng sau khi tạo, không nên chỉnh sửa, thêm hoặc xóa sản phẩm khỏi paywall vì điều này có thể ảnh hưởng đến các chỉ số của paywall.
---
# File: app-store-offers
---
---
title: "Ưu đãi trên App Store"
description: "Thiết lập và quản lý các ưu đãi trên App Store để tăng khả năng giữ chân người dùng."
---
:::info
Hãy cấu hình [sản phẩm trong cửa hàng](quickstart-products) trước khi làm theo hướng dẫn này.
:::
Ưu đãi trên App Store là các deals đặc biệt, dùng thử, hoặc giảm giá dành cho gói đăng ký tự gia hạn. Bao gồm giảm giá và ưu đãi gói giúp thu hút người dùng mới và tăng tỷ lệ chuyển đổi.
App Store có bốn loại ưu đãi, và Adapty hỗ trợ tất cả:
- **[Ưu đãi giới thiệu](#introductory-offers) dành cho người dùng mới**:
- Thời gian đăng ký miễn phí hoặc giảm giá
- Chỉ người dùng mới mới đủ điều kiện (những người chưa từng kích hoạt ưu đãi giới thiệu hoặc có gói đăng ký)
- Bạn không cần liên kết chúng với sản phẩm trong Adapty. Adapty tự động áp dụng ưu đãi cho người dùng đủ điều kiện khi họ mua sản phẩm.
- **Ưu đãi [quảng cáo](#promotional-offers) và [thu hút khách hàng cũ](#win-back-offers)**:
- Adapty tự động áp dụng các ưu đãi này khi mua hàng, nhưng bạn cần cấu hình ưu đãi trong sản phẩm và paywall trước.
- Ưu đãi quảng cáo bao gồm thời gian đăng ký miễn phí, giảm giá theo phần trăm và giảm giá cố định. Bất kỳ người dùng nào cũng có thể đủ điều kiện.
- Ưu đãi thu hút khách hàng cũ bao gồm thời gian đăng ký miễn phí hoặc giảm giá theo phần trăm. Chỉ người dùng đã hủy đăng ký mới đủ điều kiện.
- **Mã ưu đãi**: Để biết thêm thông tin, xem [Đổi mã ưu đãi trên iOS](making-purchases#redeem-offer-codes-in-ios).
:::important
Để sử dụng ưu đãi trên App Store, hãy tải [khóa đăng ký](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers) lên Adapty Dashboard.
:::
## Ưu đãi giới thiệu \{#introductory-offers\}
Adapty tự động áp dụng ưu đãi giới thiệu trên iOS nếu người dùng đủ điều kiện.
Để bật ưu đãi giới thiệu cho các sản phẩm bạn bán, bạn chỉ cần tạo chúng trong App Store Connect:
1. Mở ứng dụng của bạn trong App Store Connect và chuyển đến **Monetization > Subscriptions**.
2. Chọn một nhóm đăng ký và điều hướng đến gói đăng ký bạn cần. Gói đăng ký phải có thời hạn đã được cấu hình.
3. Nhấp vào **View all Subscription Pricing** và chuyển sang tab **Introductory offers**. Nhấp vào **Set up introductory offer**.
4. Chọn các quốc gia và khu vực nơi ưu đãi giới thiệu sẽ có hiệu lực.
5. Chọn ngày bắt đầu và ngày kết thúc cho ưu đãi giới thiệu. Nếu ưu đãi giới thiệu không có ngày kết thúc cụ thể, chọn **No end date**. Nhấp vào **Next**.
6. Chọn loại ưu đãi giới thiệu. Tùy theo lựa chọn, bạn cũng cần xác định thời hạn và giá của ưu đãi. Đọc thêm trong [tài liệu của Apple](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-introductory-offers-for-auto-renewable-subscriptions).
7. Xem lại lựa chọn của bạn và nhấp vào **Confirm**.
Sau khi hoàn tất thiết lập, bạn không cần làm gì thêm trong Adapty. Ưu đãi sẽ kích hoạt cho người dùng đủ điều kiện khi họ mua sản phẩm. Hãy đảm bảo bạn chỉ hiển thị paywall có sản phẩm này cho những người dùng đủ điều kiện nhận ưu đãi.
## Ưu đãi quảng cáo \{#promotional-offers\}
Adapty tự động áp dụng ưu đãi quảng cáo nếu người dùng đủ điều kiện. Thiết lập ưu đãi trong App Store Connect trước, sau đó thêm chúng vào sản phẩm và paywall trong Adapty:
1. Mở ứng dụng của bạn trong App Store Connect và chuyển đến **Monetization > Subscriptions** từ menu bên trái.
2. Chọn một nhóm đăng ký và điều hướng đến gói đăng ký bạn cần. Gói đăng ký phải có thời hạn đã được cấu hình.
3. Nhấp vào **View all Subscription Pricing** và chuyển sang tab **Promotional offers**. Nhấp vào **Set up promotional offer**.
4. Điền thông tin chi tiết cho ưu đãi quảng cáo. Các giá trị này không thể thay đổi sau khi tạo và sẽ được tái sử dụng, vì vậy hãy chọn cẩn thận.
- **Promotional offer reference name**: Tên ưu đãi quảng cáo. Người dùng sẽ không thấy tên này.
- **Promotional offer identifier**: Mã định danh ưu đãi quảng cáo. Bạn sẽ dùng mã này để thêm ưu đãi vào Adapty.
5. Chọn loại ưu đãi quảng cáo. Loại này xác định người dùng sẽ trả giá thấp hơn hay được dùng miễn phí. Để giảm giá, chọn **Pay as you go** hoặc **Pay up front**. Để có thời gian đăng ký miễn phí, chọn **Free**. Sau đó đặt thời hạn và giá ưu đãi. Đọc thêm trong [tài liệu của Apple](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-promotional-offers-for-auto-renewable-subscriptions).
6. Nếu cần, đặt giá khác nhau cho các quốc gia và khu vực khác nhau rồi nhấp vào **Next**.
7. Xem lại lựa chọn của bạn và nhấp vào **Confirm**.
8. [Thêm ưu đãi quảng cáo](create-offer) vào Adapty.
## Ưu đãi thu hút khách hàng cũ \{#win-back-offers\}
:::important
Trước khi tạo ưu đãi thu hút khách hàng cũ, gói đăng ký của bạn phải được App Review phê duyệt.
:::
Adapty tự động áp dụng ưu đãi thu hút khách hàng cũ nếu người dùng đủ điều kiện. Thiết lập ưu đãi trong App Store Connect trước, sau đó thêm chúng vào sản phẩm và paywall trong Adapty:
1. Mở ứng dụng của bạn trong App Store Connect và chuyển đến **Monetization > Subscriptions** từ menu bên trái.
2. Chọn một nhóm đăng ký và điều hướng đến gói đăng ký bạn cần. Gói đăng ký phải có thời hạn đã được cấu hình.
3. Nhấp vào **View all Subscription Pricing** và chuyển sang tab **Win-back offers**. Nhấp vào **Create offer**.
4. Điền thông tin chi tiết cho ưu đãi thu hút khách hàng cũ. Các giá trị này không thể thay đổi sau khi tạo.
- **Reference name**: Tên ưu đãi. Người dùng sẽ không thấy tên này.
- **Offer identifier**: Mã định danh ưu đãi. Bạn sẽ dùng mã này để thêm ưu đãi vào Adapty.
5. Cấu hình loại, thời hạn và giá ưu đãi. Đọc thêm trong [tài liệu của Apple](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-win-back-offers).
6. Xem lại lựa chọn của bạn và nhấp vào **Confirm**.
7. [Thêm ưu đãi](create-offer) vào Adapty.
## Các bước tiếp theo \{#next-steps\}
Sau khi đã thêm ưu đãi, tiếp tục thiết lập:
- Nếu bạn cũng có **ứng dụng trên Google Play**, hãy thiết lập [ưu đãi trên Google Play](google-play-offers).
- Nếu bạn có **ưu đãi quảng cáo hoặc ưu đãi thu hút khách hàng cũ**, hãy [thêm chúng vào Adapty](create-offer).
- Nếu bạn chỉ có **ưu đãi giới thiệu** và không có ưu đãi quảng cáo hoặc thu hút khách hàng cũ, bạn đã hoàn tất. Phần [Cách Adapty hoạt động với ưu đãi](create-offer#how-adapty-works-with-offers) vẫn có thể hữu ích.
---
# File: google-play-offers
---
---
title: "Ưu đãi trên Google Play"
description: "Cấu hình các ưu đãi trên Google Play để cải thiện khả năng kiếm tiền và giữ chân người dùng."
---
Trên Google Play, các ưu đãi ở bất kỳ dạng nào (dùng thử miễn phí hoặc thanh toán giảm giá) đều được thêm vào dưới dạng **offers**. Để tạo một ưu đãi, trước tiên bạn cần tạo một gói đăng ký và thêm base plan tự động gia hạn.
Ưu đãi luôn được tạo cho các base plan trong gói đăng ký. Trong ảnh chụp màn hình bên dưới, bạn có thể thấy gói đăng ký `premium_access`(1) với hai base plan: `1-month` (2) và `1-year` (3).
Để tạo ưu đãi trong Google Play Console:
1. Nhấp vào **Add offer** và chọn base plan từ danh sách.
2. Nhập ID ưu đãi. ID này sẽ được sử dụng sau trong phần phân tích và Adapty Dashboard, vì vậy hãy đặt tên có ý nghĩa.
3. Chọn tiêu chí đủ điều kiện:
1. **New customer acquisition**: ưu đãi chỉ dành cho người đăng ký mới nếu họ chưa từng sử dụng ưu đãi này trước đây. Đây là lựa chọn phổ biến nhất và nên được dùng theo mặc định.
2. **Upgrade**: ưu đãi này dành cho những khách hàng đang nâng cấp từ gói đăng ký khác. Dùng khi bạn muốn khuyến khích người dùng hiện tại chuyển lên các gói đắt hơn, ví dụ như khách hàng nâng cấp từ gói bronze lên gói gold.
3. **Developer determined**: bạn có thể kiểm soát ai được sử dụng ưu đãi này từ trong code của ứng dụng. Hãy thận trọng khi dùng trong môi trường production để tránh gian lận: người dùng có thể kích hoạt gói đăng ký miễn phí hoặc giảm giá nhiều lần. Trường hợp sử dụng phù hợp cho loại ưu đãi này là thu hút lại những người đăng ký đã rời bỏ.
4. Thêm tối đa hai giai đoạn giá cho ưu đãi của bạn. Có ba loại giai đoạn:
1. **Free trial**: gói đăng ký có thể dùng miễn phí trong một khoảng thời gian cấu hình sẵn (tối thiểu 3 ngày). Đây là ưu đãi phổ biến nhất.
2. **Single payment**: gói đăng ký rẻ hơn nếu khách hàng thanh toán trước. Ví dụ, thông thường gói hàng tháng có giá $9.99, nhưng với loại ưu đãi này, ba tháng đầu tiên chỉ có giá $19.99, giảm 30%.
3. **Discounted recurring payment**: gói đăng ký rẻ hơn trong `n` kỳ đầu tiên. Ví dụ, thông thường gói hàng tháng có giá $9.99, nhưng với loại ưu đãi này, mỗi tháng trong ba tháng đầu chỉ có giá $4.99, giảm 50%.
Một ưu đãi có thể có hai giai đoạn. Trong trường hợp đó, giai đoạn đầu phải là Free trial, và giai đoạn thứ hai là Single payment hoặc Discounted recurring payment. Chúng sẽ được áp dụng theo thứ tự này.
:::important
Lưu ý rằng các paywall được tạo bằng Adapty Paywall Builder sẽ chỉ hiển thị giai đoạn đầu tiên của ưu đãi gói đăng ký Google nhiều giai đoạn. Tuy nhiên, hãy yên tâm rằng khi người dùng mua sản phẩm, tất cả các giai đoạn ưu đãi sẽ được áp dụng đúng như cấu hình trong Google Play.
:::
5. Kích hoạt ưu đãi để sử dụng trong ứng dụng.
6. Tiếp tục với việc [thêm ưu đãi vào Adapty](create-offer).
:::note
Offer ID có thể giống nhau cho các base plan khác nhau.
:::
## Các bước tiếp theo \{#next-steps\}
Sau khi đã thêm ưu đãi, hãy tiến hành thiết lập:
- Nếu bạn có **ứng dụng trên App Store**, hãy xem [hướng dẫn App Store](app-store-offers).
- Nếu bạn **chỉ có ứng dụng trên Google Play**, hãy làm theo [hướng dẫn này](create-offer) để thêm ưu đãi vào Adapty.
---
# File: create-offer
---
---
title: "Thêm ưu đãi vào Adapty"
description: "Tạo và quản lý các ưu đãi gói đăng ký đặc biệt bằng công cụ của Adapty."
---
Adapty cho phép bạn cung cấp ưu đãi dùng thử hoặc giảm giá cho người dùng mới, hiện tại, hoặc đã huỷ đăng ký.
Sau khi thiết lập xong trong App Store Connect hoặc Google Play Console, bạn cần thêm chúng vào Adapty theo hai bước:
1. [Thêm ưu đãi vào sản phẩm trong Adapty bằng ID ưu đãi từ các cửa hàng.](#1-create-offer)
2. [Hiển thị ưu đãi trong một flow hoặc paywall.](#2-display-offer)
:::warning
Ưu đãi giới thiệu (App Store) được áp dụng tự động nếu người dùng đủ điều kiện. Không cần thêm chúng vào sản phẩm trong Adapty.
Hướng dẫn này giải thích cách cấu hình ưu đãi (App Store), ưu đãi thu hút khách hàng cũ (App Store), và tất cả các ưu đãi của Google Play.
:::
## 0. Trước khi bắt đầu \{#before-you-start\}
Trước khi thiết lập ưu đãi trong Adapty, hãy đảm bảo những điều sau:
1. Bạn đã tạo tất cả các ưu đãi cần thiết trong cửa hàng:
- [App Store](app-store-offers)
- [Google Play](google-play-offers)
2. Bạn đã tạo các [sản phẩm](create-product) trong Adapty và đã thêm ID của chúng.
3. Đối với App Store: Bạn đã tải lên [khoá in-app purchase dành cho ưu đãi](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers).
## 1. Thêm ưu đãi vào sản phẩm trong Adapty \{#1-create-offer\}
Sau khi ưu đãi (cho cả Play Store và App Store) hoặc ưu đãi thu hút khách hàng cũ (cho App Store) đã được thiết lập trong cửa hàng ứng dụng, việc thêm vào Adapty rất đơn giản:
1. Mở [**Products**](https://app.adapty.io/products) từ menu chính trong Adapty. Tìm sản phẩm bạn muốn thêm ưu đãi.
2. Tìm sản phẩm bạn muốn thêm ưu đãi. Trong cột **Actions**, nhấp vào nút **3 chấm** bên cạnh sản phẩm và chọn **Edit**.
3. Trong cửa sổ **Edit product**, nhấp **+** và chọn **Add offers**.
4. Nhấp **Add offer**.
5. Sau đó nhập thông tin chi tiết về ưu đãi cho sản phẩm.
Dưới đây là các trường thông tin cho ưu đãi:
- **Offer name**: Đặt tên cho ưu đãi để dễ nhận biết trong Adapty. Dùng bất kỳ tên nào thuận tiện cho bạn.
- **App Store Offer type**: Chọn loại ưu đãi App Store bạn đang thêm: Promotional hoặc Win-back. (Ưu đãi giới thiệu không cần thêm — chúng được áp dụng tự động nếu có.)
- **App Store Offer ID**: Đây là ID duy nhất cho ưu đãi [mà bạn đã đặt trong App Store](app-store-products).
- **Play Store Offer ID**: Tương tự, đây là ID duy nhất cho ưu đãi [mà bạn đã đặt trong Play Store](android-products).
:::tip
Nếu trường **App Store Offer ID** hoặc **Play Store Offer ID** không hoạt động, hãy chuyển sang tab **Products** và chọn ID sản phẩm.
:::
6. (tuỳ chọn) Thêm nhiều ưu đãi hơn nếu cần bằng cách nhấp **Add offer**.
7. Nhấp **Save** để thêm ưu đãi vào sản phẩm.
## 2. Hiển thị ưu đãi \{#2-display-offer\}
Sau khi ưu đãi được gắn vào sản phẩm, hãy hiển thị nó ở nơi người dùng thấy sản phẩm đó — trong một flow hoặc trong một paywall.
### Thêm ưu đãi vào flow \{#add-offer-to-flow\}
Trong [Flow Builder](adapty-flow-builder), ưu đãi được gắn vào sản phẩm trên phần tử Products. Trước tiên hãy thêm phần tử sản phẩm và gán sản phẩm vào đó — xem [Thiết lập mua hàng](paywall-product-block).
Để gắn ưu đãi:
1. Trên canvas, chọn thẻ sản phẩm cần hiển thị ưu đãi.
2. Ở bảng bên phải, dưới mục **Product**, chọn sản phẩm, sau đó chọn ưu đãi từ danh sách **Select offer (optional)**.
### Thêm ưu đãi vào paywall \{#add-offer-to-paywall\}
:::info
Bạn không thể thêm ưu đãi vào paywall đang ở trạng thái **live**. Nếu muốn thêm ưu đãi vào paywall hiện có, hãy [nhân bản](duplicate-paywalls) paywall đó và cấu hình sản phẩm trong paywall mới.
:::
Để ưu đãi hiển thị và có thể chọn được trong một [paywall](paywalls) cho người dùng ứng dụng của bạn, thực hiện các bước sau:
1. Khi tạo hoặc chỉnh sửa paywall, trong tab **General**, thêm sản phẩm mà bạn vừa thêm ưu đãi vào.
2. Chọn ưu đãi bạn đã tạo trước đó cho sản phẩm này từ danh sách **Offer**. Danh sách này chỉ hiển thị với các sản phẩm có ưu đãi.
3. Nếu cần, thêm nhiều sản phẩm và ưu đãi hơn, nhưng bạn chỉ có thể thêm một ưu đãi cho mỗi sản phẩm.
## Cách Adapty hoạt động với ưu đãi \{#how-adapty-works-with-offers\}
Lưu ý những điểm sau về cách ưu đãi hoạt động trong Adapty:
- Khi người dùng đủ điều kiện nhận ưu đãi, Adapty tự động áp dụng ưu đãi bạn đã cấu hình khi người dùng thực hiện mua hàng.
- Nếu một sản phẩm có cả ưu đãi giới thiệu lẫn ưu đãi đã được cấu hình trong App Store, người dùng đủ điều kiện sẽ nhận ưu đãi giới thiệu trước. Sau khi kết thúc thời gian đó, nếu người dùng vẫn đủ điều kiện nhận ưu đãi và bạn đã cấu hình ưu đãi này trong Adapty, nó sẽ được áp dụng khi họ cố gắng mua lại sản phẩm.
- Nếu bạn muốn kiểm soát nhiều hơn cách áp dụng ưu đãi hoặc cần bán sản phẩm không kèm ưu đãi trong một số trường hợp, bạn có một số lựa chọn:
- Cấu hình tiêu chí đủ điều kiện trong App Store hoặc Google Play Console
- Tạo sản phẩm riêng không có ưu đãi trong App Store hoặc Google Play Console
- Tạo sản phẩm riêng không có ưu đãi trong Adapty, thêm paywall chứa cả hai biến thể sản phẩm vào một [placement](placements), và sử dụng [phân khúc](segments) đối tượng để kiểm soát paywall nào được hiển thị cho từng nhóm người dùng. Ví dụ, bạn có thể tạo phân khúc dựa trên **Subscription product** hoặc **Paid access level**, hoặc sử dụng [thuộc tính tùy chỉnh](profiles-crm) để triển khai logic của riêng bạn.
---
# File: create-access-level
---
---
title: "Tạo mức độ truy cập"
description: "Tạo và gán mức độ truy cập trong Adapty để phân khúc người dùng tốt hơn."
---
Mức độ truy cập giúp bạn kiểm soát những gì người dùng có thể làm trong ứng dụng mà không cần hardcode các ID sản phẩm cụ thể. Mỗi sản phẩm xác định thời gian người dùng được hưởng một mức độ truy cập nhất định. Vì vậy, mỗi khi người dùng thực hiện mua hàng, Adapty sẽ cấp quyền truy cập vào ứng dụng trong một khoảng thời gian cụ thể (đối với gói đăng ký) hoặc vĩnh viễn (đối với sản phẩm mua trọn đời).
Khi bạn tạo ứng dụng trong Adapty Dashboard, mức độ truy cập `premium` sẽ được tự động tạo. Đây là mức độ truy cập mặc định và không thể xóa.
:::tip
Bạn cũng có thể tạo mức độ truy cập theo cách lập trình bằng [Developer CLI](developer-cli-reference#adapty-access-levels-create).
:::
Để tạo mức độ truy cập mới:
1. Vào **[Products](https://app.adapty.io/access-levels)** từ menu chính của Adapty, sau đó chọn tab **Access levels**.
2. Nhấp vào **Create access level**.
3. Trong cửa sổ **Create access level**, đặt cho nó một ID. ID này sẽ là định danh trong ứng dụng của bạn, cho phép mở khóa các tính năng bổ sung khi người dùng mua hàng. Ngoài ra, định danh này giúp phân biệt mức độ truy cập này với các mức độ truy cập khác trong ứng dụng. Hãy đảm bảo ID rõ ràng và dễ hiểu để thuận tiện cho bạn.
4. Nhấp vào **Create access level** để xác nhận việc tạo mức độ truy cập.
---
# File: assigning-access-level-to-a-product
---
---
title: "Gán mức độ truy cập cho sản phẩm"
description: "Gán mức độ truy cập cho sản phẩm để tối ưu hóa việc quản lý gói đăng ký."
---
Mỗi [Sản phẩm](product) đều cần được liên kết với một mức độ truy cập để đảm bảo người dùng nhận được nội dung tương ứng sau khi mua. Adapty tự động xác định thời hạn gói đăng ký, và thời hạn đó sẽ là ngày hết hạn của mức độ truy cập. Đối với sản phẩm trọn đời, nếu khách hàng mua, mức độ truy cập sẽ luôn hoạt động mà không có ngày hết hạn.
Để liên kết mức độ truy cập với một sản phẩm:
1. Trong khi [cấu hình sản phẩm](create-product), chọn mức độ truy cập từ danh sách **Access Level ID**.
2. Nhấn **Save**.
---
# File: give-access-level-to-specific-customer
---
---
title: "Cấp mức độ truy cập cho khách hàng cụ thể"
description: "Gán mức độ truy cập cụ thể cho khách hàng bằng các công cụ nâng cao của Adapty."
---
Bạn có thể điều chỉnh thủ công mức độ truy cập cho một khách hàng cụ thể ngay trong Adapty Dashboard. Tính năng này đặc biệt hữu ích trong các tình huống hỗ trợ. Ví dụ: nếu bạn muốn gia hạn quyền sử dụng premium cho người dùng thêm một tuần để cảm ơn họ đã để lại đánh giá tuyệt vời.
## Cấp mức độ truy cập cho khách hàng cụ thể trong Adapty Dashboard \{#give-access-level-to-a-specific-customer-in-the-adapty-dashboard\}
1. Vào **[Profiles and Segments](https://app.adapty.io/placements)** từ menu chính của Adapty.
2. Nhấp vào khách hàng bạn muốn cấp quyền truy cập.
3. Nhấp vào **Add access level**.
4. Chọn mức độ truy cập cần cấp và thời điểm hết hạn cho khách hàng này.
5. Nhấp vào **Apply**.
## Cấp mức độ truy cập cho khách hàng cụ thể qua API \{#give-access-level-to-a-specific-customer-via-api\}
Bạn cũng có thể cấp mức độ truy cập cho khách hàng từ máy chủ của mình bằng Adapty API. Tính năng này rất tiện lợi nếu bạn có các phần thưởng cho chương trình giới thiệu hoặc các sự kiện khác liên quan đến sản phẩm của bạn. Xem thêm chi tiết tại trang [Cấp mức độ truy cập bằng server-side API](api-adapty/operations/grantAccessLevel).
---
# File: local-access-levels
---
---
title: "Mức độ truy cập cục bộ"
description: "Quản lý mức độ truy cập trong trường hợp có sự cố tạm thời."
---
:::important
Lưu ý những điểm sau:
- Mức độ truy cập cục bộ được hỗ trợ trong Adapty SDK bắt đầu từ phiên bản 3.12.
- Theo mặc định, mức độ truy cập cục bộ bị tắt trên Android để tăng cường bảo mật. Nếu bạn cần dùng tính năng này, hãy bật nó khi khởi động SDK: [Android](sdk-installation-android#enable-local-access-levels), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#enable-local-access-levels-android).
:::
Mỗi sản phẩm bạn cấu hình đều có một [**mức độ truy cập**](access-level) liên kết với nó. Khi người dùng thực hiện mua hàng, Adapty SDK gán mức độ truy cập cho [hồ sơ người dùng](profiles-crm), vì vậy bạn cần sử dụng mức độ truy cập này để xác định liệu người dùng có thể truy cập nội dung trả phí trong ứng dụng hay không.
Adapty SDK rất đáng tin cậy, và rất hiếm khi máy chủ của nó không khả dụng. Tuy nhiên, ngay cả trong trường hợp hiếm gặp đó, người dùng của bạn sẽ không nhận ra điều gì bất thường.
Nếu người dùng thực hiện mua hàng nhưng Adapty không thể nhận được phản hồi, SDK sẽ chuyển sang xác minh giao dịch mua trực tiếp trong cửa hàng. Do đó, mức độ truy cập được cấp cục bộ trong ứng dụng mà không cần thiết lập thêm gì để kích hoạt. SDK xử lý điều này tự động ở phía sau, và người dùng sẽ truy cập những gì họ đã trả tiền như bình thường.
Lưu ý những điểm sau về cách hoạt động của mức độ truy cập cục bộ:
- Khi người dùng kết nối lại mạng, thông tin giao dịch sẽ tự động được đẩy lên máy chủ Adapty, sau đó áp dụng các giao dịch vào hồ sơ người dùng và trả về hồ sơ đã cập nhật cho SDK.
- Dữ liệu đã cập nhật sẽ không xuất hiện trong Adapty analytics cho đến khi dữ liệu được đẩy lên.
- Mức độ truy cập cục bộ chỉ hoạt động khi máy chủ Adapty bị ngừng hoạt động. Trong các trường hợp khác, SDK sẽ sử dụng dữ liệu đã được lưu cache.
- Mức độ truy cập cục bộ không hoạt động với các sản phẩm consumable, ngoại trừ khi sản phẩm consumable được gán loại gói đăng ký (theo tháng, theo năm, theo tuần, v.v.) trong Adapty dashboard.
---
# File: choose-meaningful-placements
---
---
title: "Chọn placement phù hợp"
description: "Tối ưu hóa placement flow và paywall với Adapty để tăng mức độ tương tác của người dùng và doanh thu."
---
Khi [tạo placement](create-placement), bạn cần cân nhắc đến luồng logic của ứng dụng và trải nghiệm người dùng mà bạn muốn tạo ra. Hầu hết các ứng dụng không cần nhiều hơn 5 [placement](placements) mà vẫn đảm bảo khả năng chạy thử nghiệm. Dưới đây là ví dụ về cách bạn có thể cấu trúc các placement:
1. **Onboarding flow:** Đây là giai đoạn người dùng tương tác lần đầu tiên với ứng dụng của bạn. Đây là cơ hội tuyệt vời để giới thiệu giá trị của ứng dụng bằng cách kết hợp các placement flow, onboarding và paywall tại đây. Hơn 80% gói đăng ký được kích hoạt trong quá trình onboarding, vì vậy hãy tập trung vào việc bán các gói đăng ký mang lại lợi nhuận cao nhất ở đây. Với Adapty, bạn có thể dễ dàng thiết lập các [flow](adapty-flow-builder), [onboarding](onboardings) và [paywall](paywalls) khác nhau cho từng đối tượng, đồng thời chạy A/B test để tìm ra lựa chọn tốt nhất cho ứng dụng. Ví dụ: bạn có thể chạy A/B test cho người dùng tại Mỹ, hiển thị các gói đăng ký giá cao hơn với xác suất 50%.
2. **Cài đặt ứng dụng:** Nếu người dùng chưa đăng ký trong quá trình onboarding, bạn có thể tạo một placement flow hoặc paywall bên trong ứng dụng. Vị trí này có thể là trong phần cài đặt ứng dụng hoặc sau khi người dùng hoàn thành một hành động mục tiêu cụ thể. Vì người dùng bên trong ứng dụng thường cân nhắc kỹ hơn trước khi đăng ký, các sản phẩm ở đây có thể có mức giá thấp hơn một chút so với giai đoạn onboarding.
3. **Promo:** Nếu người dùng vẫn chưa đăng ký sau khi xem flow hoặc paywall nhiều lần, điều đó có thể cho thấy giá quá cao hoặc họ còn do dự về việc đăng ký. Trong trường hợp này, bạn có thể hiển thị một ưu đãi đặc biệt với gói đăng ký giá phải chăng nhất hoặc thậm chí một sản phẩm quyền truy cập trọn đời. Điều này có thể giúp thu hút những người dùng nhạy cảm về giá hoặc còn hoài nghi về việc đăng ký để thực hiện mua hàng.
Hầu hết các ứng dụng sẽ có logic và placement tương tự, theo dõi hành trình người dùng và các điểm quan trọng nơi flow, paywall, onboarding hoặc A/B test có thể được hiển thị để thúc đẩy chuyển đổi và doanh thu. Bạn có thể cấu hình chúng trong từng placement để thử nghiệm và tối ưu hóa chiến lược kiếm tiền của mình.
---
# File: create-placement
---
---
title: "Tạo placement"
description: "Tạo và quản lý các placement trong Adapty để cải thiện hiệu suất flow và paywall."
---
[Placement](placements) là một vị trí cụ thể trong ứng dụng di động của bạn, nơi bạn có thể hiển thị flow, paywall, onboarding hoặc A/B test. Ví dụ, màn hình chọn gói đăng ký có thể xuất hiện trong flow khởi động, trong khi một consumable (như đồng xu vàng) có thể hiện ra khi người dùng hết xu trong game.
Bạn có thể hiển thị cùng một hoặc các flow, paywall, onboarding, hay A/B test khác nhau ở nhiều placement hoặc cho các phân khúc người dùng khác nhau — gọi là "đối tượng" trong Adapty.
Xem phần [Chọn placement có ý nghĩa](choose-meaningful-placements) để biết thêm các gợi ý về cách chọn placement phù hợp.
:::tip
Bạn cũng có thể tạo placement theo cách lập trình bằng [Developer CLI](developer-cli-reference#adapty-placements-create).
:::
:::info
Mặc dù quy trình tạo placement tương tự nhau cho flow, paywall và onboarding, bạn không thể tạo một placement phục vụ nhiều hơn một loại — mỗi loại placement xử lý các chỉ số khác nhau.
:::
## Tạo và cấu hình placement \{#create-and-configure-a-placement\}
1. Vào **[Placements](https://app.adapty.io/placements)** từ menu chính của Adapty. Chuyển sang tab **Flows**, **Paywalls**, hoặc **Onboardings** tùy theo loại placement bạn muốn tạo.
2. Nhấp vào **Create placement**.
3. Nhập **Placement name**. Đây là định danh nội bộ trong Adapty Dashboard. Bạn có thể chỉnh sửa nó sau nếu cần.
4. Nhập **Placement ID**. Bạn sẽ dùng ID này trong Adapty SDK để gọi các [flow](adapty-flow-builder), [paywall](paywalls), [onboarding](onboardings) và [A/B test](ab-tests) của placement. Bạn không thể chỉnh sửa nó sau vì đây là giá trị duy nhất cho mỗi placement.
Tiếp theo, gán một flow, paywall, onboarding hoặc A/B test cho placement. Adapty hỗ trợ [đối tượng](audience) — các phân khúc người dùng dựa trên [phân khúc](segments) — để bạn có thể hiển thị nội dung khác nhau cho các nhóm người dùng khác nhau. Nếu bạn không cần nhắm mục tiêu, đối tượng mặc định *All users* sẽ bao gồm tất cả mọi người.
:::note
Để tiếp tục, hãy đảm bảo rằng bạn đã tạo một flow, paywall, onboarding, hoặc A/B test mà bạn muốn chạy và một đối tượng mà bạn muốn chỉ định.
:::
1. Trong cửa sổ **Placements/ Your placement**, thêm một flow, paywall, onboarding, hoặc A/B test để hiển thị cho đối tượng mặc định *All users*. Để làm điều này, nhấp vào nút **Run flow**, **Run paywall**, hoặc **Run A/B test** (nhãn phụ thuộc vào loại placement), sau đó chọn flow, paywall, onboarding, hoặc A/B test mong muốn từ danh sách thả xuống.
2. Nếu bạn muốn sử dụng nhiều hơn một đối tượng trong placement để tạo nội dung cá nhân hóa phù hợp với các nhóm người dùng khác nhau, nhấp vào nút **Add audience** và chọn phân khúc người dùng mong muốn từ danh sách.
File CSV được xuất chứa các thông tin sau về placement của bạn:
- Placement ID
- Tên placement
- Tên đối tượng
- Tên phân khúc
- Tên A/B test xuyên placement
- Tên A/B test
- Tên flow, tên paywall hoặc tên onboarding (tùy thuộc vào tab bạn đã xuất)
:::note
A/B test xuyên placement không được hỗ trợ cho placement của flow, vì vậy cột đó sẽ trống trong các lần xuất flow.
:::
---
# File: delete-placement
---
---
title: "Xóa placement"
description: "Tìm hiểu cách xóa một placement trong Adapty mà không ảnh hưởng đến hiệu suất flow hoặc paywall của bạn."
---
[Placement](placements) chỉ định một vị trí cụ thể trong ứng dụng di động của bạn, nơi flow, paywall, onboarding hoặc A/B test có thể được hiển thị.
:::danger
Mặc dù bạn có thể xóa bất kỳ placement nào, nhưng điều quan trọng là phải đảm bảo rằng bạn không xóa một placement đang được sử dụng trong ứng dụng di động của mình. Việc xóa một placement của flow hoặc paywall đang hoạt động sẽ dẫn đến paywall dự phòng cục bộ được hiển thị vĩnh viễn nếu bạn đã [thiết lập nó](fallback-paywalls), và bạn sẽ không bao giờ có thể thay thế nó bằng flow hoặc paywall động trong các phiên bản ứng dụng đã phát hành.
:::
Để xóa một placement hiện có:
1. Vào **[Placements](https://app.adapty.io/placements)** từ menu chính của Adapty. Chuyển sang tab **Flows**, **Paywalls** hoặc **Onboardings** tùy thuộc vào loại placement bạn muốn xóa.
2. Nhấp vào nút **3 chấm** bên cạnh placement và chọn tùy chọn **Delete**.
3. Trong cửa sổ **Delete placement** vừa mở, nhập tên sản phẩm bạn sắp xóa.
4. Nhấp vào nút **Delete forever** để xác nhận việc xóa.
---
# File: add-audience-paywall-ab-test
---
---
title: "Thêm đối tượng và flow, paywall, hoặc A/B test vào placement"
description: "Chạy A/B test trên các flow và paywall cho các phân khúc đối tượng khác nhau trong Adapty."
---
**Đối tượng** trong Adapty là các nhóm người dùng được xác định theo [phân khúc](segments). Chúng cho phép bạn hiển thị flow, paywall, onboarding và A/B test đến đúng những người dùng cần xem. Hãy xây dựng phân khúc bằng các bộ lọc để đảm bảo mỗi nhóm nhận được nội dung phù hợp.
Khi bạn thêm một đối tượng vào [placement](placements), bạn đang nhắm mục tiêu flow, paywall, onboarding hoặc A/B test vào một nhóm người dùng cụ thể. Việc liên kết đối tượng với placement đảm bảo đúng người dùng thấy đúng nội dung vào đúng thời điểm trong hành trình sử dụng ứng dụng của họ.
Mở placement mà bạn muốn thêm flow, paywall, onboarding hoặc A/B test, hoặc tạo mới trong menu [Placements](https://app.adapty.io/placements).
:::note
Để tiếp tục, hãy đảm bảo rằng bạn đã tạo một flow, paywall, onboarding, hoặc A/B test mà bạn muốn chạy và một đối tượng mà bạn muốn chỉ định.
:::
1. Trong cửa sổ **Placements/ Your placement**, thêm một flow, paywall, onboarding, hoặc A/B test để hiển thị cho đối tượng mặc định *All users*. Để làm điều này, nhấp vào nút **Run flow**, **Run paywall**, hoặc **Run A/B test** (nhãn phụ thuộc vào loại placement), sau đó chọn flow, paywall, onboarding, hoặc A/B test mong muốn từ danh sách thả xuống.
2. Nếu bạn muốn sử dụng nhiều hơn một đối tượng trong placement để tạo nội dung cá nhân hóa phù hợp với các nhóm người dùng khác nhau, nhấp vào nút **Add audience** và chọn phân khúc người dùng mong muốn từ danh sách.
Trong trường hợp này, chúng ta dựa vào thứ tự ưu tiên đối tượng. Thứ tự ưu tiên đối tượng là một thứ tự số, trong đó #1 là cao nhất. Nó hướng dẫn trình tự kiểm tra các đối tượng. Nói đơn giản hơn, thứ tự ưu tiên đối tượng giúp Adapty quyết định đối tượng nào được áp dụng trước khi chọn paywall, onboarding hay A/B test để hiển thị. Nếu mức độ ưu tiên của một đối tượng thấp, những người dùng có thể đủ điều kiện sẽ bị bỏ qua. Thay vào đó, họ có thể được chuyển đến một đối tượng khác có mức độ ưu tiên cao hơn.
Các đối tượng crossplacement, tức là những đối tượng được tạo cho [A/B test crossplacement](ab-tests#ab-test-types), luôn có mức độ ưu tiên cao hơn các đối tượng thông thường.
Đối tượng "All users" luôn có mức độ ưu tiên thấp nhất vì đây là đối tượng dự phòng và bao gồm tất cả những người không khớp với bất kỳ đối tượng nào khác.
Để điều chỉnh thứ tự ưu tiên đối tượng cho một placement:
1. Khi tạo mới hoặc chỉnh sửa một placement hiện có, nhấp vào **Edit priority**. Nút này chỉ hiển thị khi có ít nhất ba đối tượng được thêm vào một placement ("All users" và hai đối tượng khác). Nếu ít hơn, thứ tự đã rõ ràng — đối tượng "All users" luôn đứng cuối.
2. Trong cửa sổ **Edit audience priorities** vừa mở, kéo và thả các đối tượng để sắp xếp lại thứ tự cho đúng.
3. Nhấp vào nút **Save**.
---
# File: placement-metrics
---
---
title: "Chỉ số placement"
description: "Phân tích chỉ số placement trong Adapty để cải thiện hiệu suất paywall."
---
Với Adapty, bạn có thể tạo và quản lý nhiều placement trong ứng dụng, mỗi placement được liên kết với các paywall hoặc A/B test riêng biệt. Sự linh hoạt này giúp bạn nhắm mục tiêu đến các phân khúc người dùng cụ thể, thử nghiệm các ưu đãi hoặc mô hình định giá khác nhau, và tối ưu hóa chiến lược kiếm tiền của ứng dụng.
Để thu thập thông tin chi tiết về hiệu suất của các placement và mức độ tương tác của người dùng với ưu đãi, Adapty theo dõi nhiều tương tác của người dùng và các giao dịch liên quan đến paywall được hiển thị. Hệ thống phân tích mạnh mẽ ghi lại các chỉ số bao gồm lượt xem, lượt xem duy nhất, lượt mua, trial, hoàn tiền, tỷ lệ chuyển đổi và doanh thu.
Các chỉ số được thu thập liên tục cập nhật theo thời gian thực và có thể được truy cập, phân tích tiện lợi qua dashboard thân thiện với người dùng của Adapty. Bạn có thể tùy chỉnh khoảng thời gian phân tích, áp dụng bộ lọc theo các thông số khác nhau, và so sánh chỉ số giữa các placement, phân khúc người dùng hoặc sản phẩm.
Chỉ số placement có sẵn trong danh sách placement, nơi bạn có thể xem tổng quan hiệu suất của tất cả các placement. Chế độ xem tổng quan này cung cấp chỉ số tổng hợp cho từng placement, cho phép bạn so sánh hiệu suất và xác định xu hướng.
Để phân tích chi tiết hơn từng placement, bạn có thể điều hướng đến trang chỉ số chi tiết của placement. Trên trang này, bạn sẽ tìm thấy các chỉ số toàn diện dành riêng cho placement được chọn. Các chỉ số này cung cấp thông tin sâu hơn về cách một placement cụ thể đang hoạt động, giúp bạn đánh giá hiệu quả và đưa ra quyết định dựa trên dữ liệu.
### Lọc chỉ số theo ngày cài đặt \{#filter-metrics-by-install-date\}
---
no_index: true
---
Các chỉ số về paywall, trial và mua hàng có thể được nhóm theo hai loại ngày khác nhau:
- **Ngày sự kiện** — khi paywall được xem, trial bắt đầu, hoặc giao dịch mua xảy ra.
- **Ngày cài đặt** — khi người dùng lần đầu mở ứng dụng.
Hai chế độ xem này có thể hiển thị các con số rất khác nhau cho cùng một khoảng thời gian. Hộp kiểm **Filter metrics by install date** kiểm soát chế độ nào được dashboard sử dụng:
- **Bỏ chọn (mặc định)**: Các chỉ số được nhóm theo ngày sự kiện.
- **Đã chọn**: Các chỉ số được nhóm theo ngày cài đặt.
**Ví dụ.** Bạn đặt khoảng thời gian từ ngày 1–30 tháng 4 và xem các trial.
- **Bỏ chọn**: Hiển thị các trial *bắt đầu* trong tháng 4, bất kể người dùng đó cài đặt ứng dụng khi nào.
- **Đã chọn**: Hiển thị các trial từ những người dùng *đã cài đặt* trong tháng 4, bất kể trial của họ bắt đầu khi nào.
Dùng chế độ xem theo ngày cài đặt để đo hiệu quả thu hút người dùng cho một cohort cụ thể. Dùng chế độ xem theo ngày sự kiện để đo hoạt động paywall hoặc onboarding trong một khoảng thời gian cụ thể.
### Điều khiển chỉ số \{#metrics-controls\}
Hệ thống hiển thị chỉ số dựa trên khoảng thời gian đã chọn và sắp xếp chúng theo thông số cột bên trái với bốn cấp độ thụt lề.
#### Tùy chọn xem dữ liệu chỉ số \{#view-options-for-metrics-data\}
Trang chỉ số placement cung cấp hai tùy chọn xem dữ liệu: theo paywall và theo đối tượng.
Trong chế độ xem theo paywall, chỉ số được nhóm theo các placement liên kết với paywall. Điều này cho phép người dùng phân tích chỉ số theo các placement khác nhau.
Trong chế độ xem theo đối tượng, chỉ số được nhóm theo đối tượng mục tiêu của paywall. Người dùng có thể đánh giá chỉ số dành riêng cho từng phân khúc đối tượng.
#### Khoảng thời gian \{#time-ranges\}
Bạn có thể chọn từ nhiều khoảng thời gian để phân tích dữ liệu chỉ số, cho phép tập trung vào các khoảng thời gian cụ thể như ngày, tuần, tháng hoặc phạm vi ngày tùy chỉnh.
#### Bộ lọc và nhóm có sẵn \{#available-filters-and-grouping\}
:::link
Bài viết chính: [Điều khiển Analytics](controls-filters-grouping-compare-proceeds)
:::
Adapty cung cấp các công cụ mạnh mẽ để lọc và tùy chỉnh phân tích chỉ số theo nhu cầu của bạn. Với trang chỉ số của Adapty, bạn có thể truy cập nhiều khoảng thời gian, tùy chọn nhóm và khả năng lọc.
- ✅ Lọc theo: Đối tượng, paywall, nhóm paywall, placement, quốc gia, cửa hàng.
- ✅ Nhóm theo: Phân khúc, cửa hàng và sản phẩm
#### Biểu đồ chỉ số đơn \{#single-metrics-chart\}
Một trong những thành phần chính của trang chỉ số placement là phần biểu đồ, trực quan hóa các chỉ số được chọn và hỗ trợ phân tích dễ dàng.
Phần biểu đồ trên trang chỉ số placement bao gồm biểu đồ thanh ngang trực quan hóa các giá trị chỉ số được chọn. Mỗi thanh trong biểu đồ tương ứng với một giá trị chỉ số và có kích thước tỷ lệ, giúp dễ dàng nắm bắt dữ liệu ngay lập tức. Đường ngang biểu thị khoảng thời gian đang được phân tích, và cột dọc hiển thị các giá trị số của chỉ số. Tổng giá trị của tất cả các chỉ số được hiển thị bên cạnh biểu đồ.
Ngoài ra, nhấp vào biểu tượng mũi tên ở góc trên bên phải của phần biểu đồ sẽ mở rộng chế độ xem, hiển thị các chỉ số được chọn trên toàn bộ đường của biểu đồ.
#### Tóm tắt tổng chỉ số \{#total-metrics-summary\}
Bên cạnh biểu đồ chỉ số đơn, phần tóm tắt tổng chỉ số hiển thị các giá trị tích lũy cho các chỉ số được chọn tại một thời điểm cụ thể, với khả năng thay đổi chỉ số hiển thị bằng menu thả xuống.
### Định nghĩa chỉ số \{#metrics-definitions\}
Khai thác sức mạnh của chỉ số placement với các định nghĩa toàn diện. Từ doanh thu đến tỷ lệ chuyển đổi, thu thập thông tin chi tiết có giá trị giúp tăng cường chiến lược kiếm tiền và thúc đẩy thành công cho ứng dụng của bạn.
#### Doanh thu \{#revenue\}
Chỉ số này đại diện cho tổng số tiền tạo ra tính bằng USD từ các lần mua và gia hạn trong các placement cụ thể. Lưu ý rằng phép tính doanh thu không bao gồm hoa hồng Apple App Store hoặc Google Play Store và được tính trước khi khấu trừ bất kỳ phí nào.
#### Proceeds \{#proceeds\}
Chỉ số này đại diện cho số tiền thực tế mà chủ ứng dụng nhận được tính bằng USD từ các lần mua và gia hạn trong các placement cụ thể sau khi khấu trừ hoa hồng áp dụng của Apple App Store hoặc Google Play Store. Nó phản ánh doanh thu ròng đóng góp trực tiếp vào thu nhập của ứng dụng. Để biết thêm thông tin về cách tính proceeds, bạn có thể tham khảo [tài liệu](analytics-cohorts#revenue-vs-proceeds) Adapty.
#### ARPPU \{#arppu\}
ARPPU là viết tắt của Average revenue per paying user (Doanh thu trung bình trên mỗi người dùng trả phí), đo lường doanh thu trung bình được tạo ra trên mỗi người dùng trả phí trong các placement cụ thể. Nó được tính bằng tổng doanh thu chia cho số người dùng trả phí duy nhất. Ví dụ: nếu tổng doanh thu là $15.000 và có 1.000 người dùng trả phí, ARPPU sẽ là $15.
#### ARPAS \{#arpas\}
ARPAS, hay Average revenue per active subscriber (Doanh thu trung bình trên mỗi người đăng ký đang hoạt động), cho phép bạn đo lường doanh thu trung bình được tạo ra trên mỗi người đăng ký đang hoạt động trong các placement cụ thể. Nó được tính bằng cách chia tổng doanh thu cho số người đăng ký đã kích hoạt trial hoặc gói đăng ký. Ví dụ: nếu tổng doanh thu là $5.000 và có 1.000 người đăng ký, ARPAS sẽ là $5. Chỉ số này giúp đánh giá tiềm năng kiếm tiền trung bình trên mỗi người đăng ký.
#### ARPU \{#arpu\}
Chỉ dành cho placement onboarding. ARPU là doanh thu trung bình trên mỗi người dùng đã xem onboarding. Được tính bằng tổng doanh thu chia cho số lượng người xem duy nhất.
#### Unique CR to purchases \{#unique-cr-to-purchases\}
Tỷ lệ chuyển đổi duy nhất sang lần mua được tính bằng cách chia số lần mua trong các placement cụ thể cho số lượt xem duy nhất. Nó tập trung vào tỷ lệ mua so với số lượt xem duy nhất, cung cấp thông tin về hiệu quả chuyển đổi khách truy cập duy nhất trong các placement cụ thể thành khách hàng trả phí.
#### CR to purchases \{#cr-to-purchases\}
Tỷ lệ chuyển đổi sang lần mua được tính bằng cách chia số lần mua trong các placement cụ thể cho tổng số lượt xem paywall. Nó chỉ ra tỷ lệ phần trăm lượt xem trong các placement cụ thể dẫn đến lần mua, cung cấp thông tin về hiệu quả của paywall trong việc chuyển đổi người dùng thành khách hàng trả phí.
#### Unique CR to trials \{#unique-cr-to-trials\}
Tỷ lệ chuyển đổi duy nhất sang trial được tính bằng cách chia số lượt bắt đầu trial trong các placement cụ thể cho số lượt xem duy nhất. Nó đo lường tỷ lệ phần trăm lượt xem duy nhất trong các placement cụ thể dẫn đến kích hoạt trial, cung cấp thông tin về hiệu quả của paywall trong việc chuyển đổi khách truy cập duy nhất thành người dùng trial.
#### Purchases \{#purchases\}
Purchases đại diện cho tổng tích lũy của các giao dịch được thực hiện trên paywall trong các placement cụ thể. Các giao dịch sau đây được bao gồm trong chỉ số này (không bao gồm gia hạn):
- Các lần mua mới được thực hiện trực tiếp trong các placement cụ thể.
- Chuyển đổi trial của các trial được kích hoạt ban đầu trong các placement cụ thể.
- Hạ cấp, nâng cấp và chuyển đổi chéo gói đăng ký được thực hiện trong các placement cụ thể.
- Khôi phục gói đăng ký trong các placement cụ thể, chẳng hạn khi gói đăng ký được tái kích hoạt sau khi hết hạn mà không có tự động gia hạn.
Bằng cách xem xét các loại giao dịch khác nhau này, chỉ số purchases cung cấp cái nhìn toàn diện về hoạt động thu thập và kiếm tiền tổng thể trong các placement cụ thể.
#### Trials \{#trials\}
Chỉ số trials đại diện cho tổng số trial đã được kích hoạt trong các placement cụ thể. Nó phản ánh số lượng người dùng đã bắt đầu thời gian dùng thử qua paywall trong các placement đó. Chỉ số này giúp theo dõi hiệu quả của ưu đãi dùng thử và cung cấp thông tin về mức độ tương tác của người dùng và tỷ lệ chuyển đổi từ trial sang gói đăng ký trả phí.
#### Trials canceled \{#trials-canceled\}
Chỉ số trials canceled đại diện cho số lượng trial trong các placement cụ thể mà tính năng tự động gia hạn đã bị tắt. Điều này xảy ra khi người dùng hủy đăng ký thủ công khỏi trial, cho thấy quyết định không tiếp tục gói đăng ký sau khi thời gian dùng thử kết thúc. Theo dõi trials canceled cung cấp thông tin có giá trị về hành vi người dùng và cho phép bạn hiểu tỷ lệ người dùng từ chối trial trong các placement cụ thể.
#### Refunds \{#refunds\}
Chỉ số refunds đại diện cho số lần mua và gói đăng ký được hoàn tiền trong các placement cụ thể. Điều này bao gồm các giao dịch đã bị đảo ngược hoặc hoàn tiền do nhiều lý do khác nhau, chẳng hạn như yêu cầu của khách hàng, vấn đề thanh toán, hoặc bất kỳ chính sách hoàn tiền áp dụng nào khác.
#### Refund rate \{#refund-rate\}
Tỷ lệ hoàn tiền được tính bằng cách chia số lần hoàn tiền trong các placement cụ thể cho số lần mua lần đầu (không bao gồm gia hạn). Ví dụ: nếu có 5 lần hoàn tiền và 1.000 lần mua lần đầu, tỷ lệ hoàn tiền sẽ là 0,5%.
#### Views \{#views\}
Chỉ số views đại diện cho tổng số lần paywall trong các placement cụ thể đã được người dùng xem. Mỗi lần người dùng truy cập paywall trong các placement đó, nó được tính là một lượt xem riêng biệt. Theo dõi views giúp bạn hiểu mức độ tương tác và tương tác của người dùng với paywall, cung cấp thông tin về hành vi người dùng và hiệu quả của vị trí và thiết kế paywall trong các khu vực cụ thể của ứng dụng.
#### Unique views \{#unique-views\}
Chỉ số unique views đại diện cho số lần duy nhất mà paywall trong các placement cụ thể đã được người dùng xem. Không giống như tổng views tính mỗi lần truy cập là một lượt xem riêng, unique views chỉ tính một lần truy cập của mỗi người dùng vào paywall trong các placement đó, bất kể họ truy cập bao nhiêu lần. Theo dõi unique views giúp cung cấp thước đo chính xác hơn về mức độ tương tác của người dùng và phạm vi tiếp cận của paywall trong các placement cụ thể, vì nó tập trung vào người dùng cá nhân thay vì tổng số lần truy cập.
#### Completions & unique completions \{#completions--unique-completions\}
Chỉ dành cho placement onboarding. Completions đếm số lần người dùng hoàn thành placement onboarding, tức là họ đi từ màn hình đầu tiên đến màn hình cuối cùng. Nếu ai đó hoàn thành hai lần, đó là hai **completions** nhưng chỉ một **unique completion**.
#### Unique completions rate \{#unique-completions-rate\}
Chỉ dành cho placement onboarding. Số unique completion chia cho số unique view. Chỉ số này giúp bạn hiểu cách mọi người tương tác với placement onboarding và thực hiện thay đổi nếu bạn nhận thấy mọi người bỏ qua nó.
---
# File: create-paywall
---
---
title: "Tạo paywall"
description: "Tìm hiểu cách tạo paywall có tỷ lệ chuyển đổi cao bằng Paywall Builder của Adapty."
---
[Paywall](paywalls) là một cấu hình trong Adapty xác định những 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 trong ứng dụng của bạn.
Bạn cần có paywall bất kể bạn hiển thị nó như thế nào:
- [**Paywall Builder**](adapty-paywall-builder): Thiết kế màn hình trong trình chỉnh sửa không cần code. Adapty sẽ hiển thị và xử lý các giao dịch mua.
- **Paywall tùy chỉnh**: Tự triển khai giao diện của bạn và sử dụng cấu hình paywall để lấy sản phẩm.
Sau khi tạo xong, hãy gán paywall vào một [placement](placements) — placement kiểm soát paywall nào người dùng sẽ thấy. Các sản phẩm trên paywall đang hoạt động sẽ được cố định, vì vậy các chỉ số của nó luôn phản ánh cùng một tổ hợp sản phẩm, giúp bạn so sánh hiệu suất giữa các sản phẩm và mức giá khác nhau.
:::tip
Bạn cũng có thể tạo paywall theo cách lập trình bằng [Developer CLI](developer-cli-reference#adapty-paywalls-create).
:::
## Các bước tiếp theo \{#next-steps\}
Sau khi đã tạo paywall đầu tiên:
1. Thêm nó vào một [placement](placements). ID của placement sẽ là những thứ duy nhất được hardcode. Bạn sẽ dùng chúng để lấy sản phẩm cần bán.
2. Cách bạn làm việc với paywall tiếp theo phụ thuộc vào cách triển khai của bạn:
- Nếu bạn muốn dùng [Adapty Paywall Builder](adapty-paywall-builder), hãy thiết kế paywall trong trình chỉnh sửa không cần code. Adapty sẽ hiển thị paywall và xử lý logic mua hàng, trong khi bạn chỉ cần hiển thị paywall trong code ứng dụng.
- Nếu bạn có paywall tùy chỉnh muốn sử dụng, hãy xem hướng dẫn triển khai in-app purchase với Adapty cho nền tảng của bạn:
- [iOS](ios-implement-paywalls-manually)
- [Android](android-implement-paywalls-manually)
- [React Native](react-native-implement-paywalls-manually)
- [Flutter](flutter-implement-paywalls-manually)
- [Unity](unity-implement-paywalls-manually)
- [Kotlin Multiplatform](kmp-implement-paywalls-manually)
---
# File: customize-paywall-with-remote-config
---
---
title: "Thiết kế paywall với Remote Config"
description: "Tùy chỉnh paywall của bạn với Remote Config trong Adapty để nhắm mục tiêu tốt hơn."
---
:::important
Hướng dẫn này đề cập đến Remote Config cho các paywall thông thường. Đối với Flow Builder, xem [Tùy chỉnh flow với remote config](customize-flow-with-remote-config).
:::
Paywall Remote Config là một công cụ mạnh mẽ cung cấp các tùy chọn cấu hình linh hoạt. Nó cho phép sử dụng các JSON payload tùy chỉnh để điều chỉnh paywall của bạn một cách chính xác. Với nó, bạn có thể định nghĩa các tham số khác nhau như tiêu đề, hình ảnh, phông chữ, màu sắc và nhiều hơn nữa.
3. Chuyển sang tab **Remote config**.
Remote Config có 2 chế độ xem:
- [Bảng](customize-paywall-with-remote-config#table-view-of-the-remote-config)
- [JSON](customize-paywall-with-remote-config#json-view-of-the-remote-config)
Cả chế độ xem **Bảng** và **JSON** đều bao gồm các thành phần cấu hình giống nhau. Sự khác biệt duy nhất là theo sở thích cá nhân, với điểm khác biệt duy nhất là chế độ xem bảng cung cấp menu ngữ cảnh, có thể hữu ích khi sửa lỗi bản địa hóa.
Bạn có thể chuyển đổi giữa các chế độ xem bằng cách nhấn vào tab **Bảng** hoặc **JSON** bất cứ khi nào cần.
Dù bạn chọn chế độ xem nào để tùy chỉnh paywall, bạn vẫn có thể truy cập dữ liệu này từ SDK sau đó thông qua thuộc tính `remoteConfig` hoặc `remoteConfigString` của `AdaptyPaywall`, và thực hiện một số điều chỉnh cho paywall của bạn. Bạn cũng có thể cập nhật các giá trị Remote Config theo lập trình thông qua [server-side API](api-adapty/operations/updatePaywall) để thay đổi cấu hình paywall một cách linh động mà không cần cập nhật thủ công trên dashboard. Dưới đây là một số ví dụ về cách bạn có thể sử dụng Remote Config.
### Chế độ xem Bảng của Remote Config \{#table-view-of-the-remote-config\}
Nếu bạn không quen làm việc với code và cần sửa một số giá trị trong JSON, Adapty có chế độ xem **Bảng** dành cho bạn.
Đây là bản sao JSON của bạn ở định dạng bảng dễ đọc và hiểu. Mã màu giúp nhận biết các kiểu dữ liệu khác nhau.
Để thêm một key, nhấn nút **Add row**. Chúng tôi tự động kiểm tra việc ánh xạ giá trị và kiểu dữ liệu, đồng thời hiển thị cảnh báo nếu các chỉnh sửa của bạn có thể dẫn đến JSON không hợp lệ.
Các tùy chọn hàng bổ sung chủ yếu hữu ích cho [bản địa hóa paywall](add-remote-config-locale):
Bây giờ đã đến lúc [tạo một placement](create-placement) và thêm paywall vào đó. Sau đó, bạn có thể
4. Nhấp vào **Locales** và chọn các ngôn ngữ bạn muốn hỗ trợ. Lưu thay đổi để thêm các ngôn ngữ này vào paywall.
Bây giờ, bạn có thể dịch nội dung thủ công, dùng AI, hoặc xuất file bản địa hóa để gửi cho dịch giả bên ngoài.
## Dịch paywall bằng AI \{#translate-paywalls-with-ai\}
Dịch thuật bằng AI là cách nhanh chóng và hiệu quả để bản địa hóa paywall.
Bạn có thể dịch cả giá trị **String** và **List**. Theo mặc định, tất cả các dòng đều được chọn (được tô màu tím). Các dòng đã được dịch sẽ được đánh dấu màu xanh lá và mặc định sẽ không được đưa vào lần dịch mới. Các dòng chưa được chọn hoặc chưa dịch sẽ hiển thị màu xám.
1. Chọn các dòng cần dịch. Bạn nên bỏ chọn các dòng chứa ID, URL và biến để tránh AI dịch nhầm chúng.
2. Chọn các ngôn ngữ cần dịch.
3. Nhấp **AI Translate** để áp dụng bản dịch. Các dòng được chọn sẽ được dịch và thêm vào paywall, với các dòng đã dịch được đánh dấu màu xanh lá.
## Xuất file bản địa hóa để dịch thuật bên ngoài \{#exporting-localization-files-for-external-translation\}
Mặc dù bản địa hóa bằng AI đang trở thành xu hướng phổ biến, bạn có thể muốn một phương pháp đáng tin cậy hơn, chẳng hạn như sử dụng dịch giả chuyên nghiệp hoặc công ty dịch thuật có uy tín. Trong trường hợp đó, bạn có thể xuất file bản địa hóa để chia sẻ với dịch giả, sau đó nhập lại kết quả đã dịch vào Adapty.
Nhấp nút **Export** sẽ tạo các file `.json` riêng lẻ cho từng ngôn ngữ, được đóng gói thành một file nén duy nhất. Nếu bạn chỉ cần một file, bạn có thể xuất trực tiếp từ menu của ngôn ngữ đó.
Sau khi nhận được các file đã dịch, dùng nút **Import** để tải lên toàn bộ hoặc từng file một. Adapty sẽ tự động kiểm tra các file để đảm bảo chúng đúng định dạng.
### Định dạng file nhập \{#import-file-format\}
Để đảm bảo nhập thành công, file nhập phải đáp ứng các yêu cầu sau:
- **Tên file và phần mở rộng:**
Tên file phải khớp với ngôn ngữ mà nó đại diện và có phần mở rộng `.json`. Bạn có thể kiểm tra và sao chép tên ngôn ngữ trong Adapty Dashboard. Nếu tên không được nhận dạng, quá trình nhập sẽ thất bại.
- **JSON hợp lệ:**
File phải là JSON hợp lệ. Nếu không, quá trình nhập sẽ thất bại.
## Bản địa hóa thủ công \{#manual-localization\}
Đôi khi, bạn có thể muốn chỉnh sửa bản dịch, thêm hình ảnh khác nhau cho từng ngôn ngữ, hoặc thậm chí điều chỉnh trực tiếp cấu hình Remote Config.
1. Chọn phần tử bạn muốn dịch và nhập giá trị mới. Bạn có thể cập nhật cả giá trị **String** và **List**, hoặc thay thế hình ảnh bằng những hình phù hợp hơn với ngôn ngữ đó.
2. Tận dụng menu ngữ cảnh trong ngôn ngữ tiếng Anh để xử lý các vấn đề bản địa hóa một cách hiệu quả:
- **Copy this value to all locales**: Ghi đè mọi thay đổi đã thực hiện trong các ngôn ngữ không phải tiếng Anh cho hàng đã chọn, thay thế bằng giá trị từ ngôn ngữ tiếng Anh.
- **Revert all row changes to original values**: Hủy bỏ mọi thay đổi đã thực hiện trong phiên hiện tại và khôi phục các giá trị về trạng thái đã lưu lần cuối.
Sau khi thêm ngôn ngữ vào paywall, hãy đảm bảo bạn triển khai mã ngôn ngữ đúng cách trong code ứng dụng. Xem
3. ⚠️ Nếu bạn chọn Stripe, hãy đảm bảo bạn đang dùng các khóa từ môi trường **Test Mode** dù giao diện hiển thị là **Sandbox**. Nếu không, web paywall của bạn sẽ không hoạt động. **Sandboxes** trong Stripe chưa được hỗ trợ.
### Thiết lập xác minh domain Apple Pay \{#set-up-apple-pay-domain-verification\}
Trong **Settings > Domains**, chọn nhà cung cấp thanh toán chính để sử dụng cho việc xác minh domain. Sau đó, xác minh các domain paywall của bạn với nhà cung cấp tương ứng:
**Stripe**:
1. Truy cập [Payment method domain settings](https://dashboard.stripe.com/settings/payment_method_domains) và nhấn **Add a new domain**.
2. Thêm `app.funnelfox.com` và subdomain paywall cá nhân của bạn (có dạng `paywalls-....fnlfx.com`). Để tìm subdomain của bạn, vào **Settings > Domains** và sao chép giá trị **Hosted subdomain**.
**Paddle**:
1. Trong Paddle console, vào **Checkout > Website approval** và nhấn **Add a new domain**.
2. Thêm `app.funnelfox.com` và subdomain paywall cá nhân của bạn (có dạng `paywalls-....fnlfx.com`). Để tìm subdomain của bạn, vào **Settings > Domains** và sao chép giá trị **Hosted subdomain**.
Quá trình phê duyệt trong Paddle là thủ công, vì vậy bạn cần đợi cho đến khi domain chuyển từ trạng thái `Pending` sang `Approved`.
**FunnelFox Billing**:
Làm theo [hướng dẫn tích hợp FunnelFox Billing](https://funnelfox.com/docs/billing/integration-billing-funnelfox).
**SolidGate**:
1. Trong Solidgate Dashboard của bạn, vào **Developers > Apple Pay Domains**.
2. Nhấn **+ Add new domain** và dán domain dự án của bạn (từ **Settings > Domains** trong FunnelFox). Thêm custom domain của bạn nếu có.
3. Để sử dụng Apple Pay ở chế độ preview, cũng cần thêm `http://app.funnelfox.com/`.
## Tạo và cấu hình web paywall \{#create-and-configure-a-web-paywall\}
1. Trên trang danh sách web paywall, nhấn **Create a paywall**.
2. Nhập tên paywall và nhấn **Create**.
3. Bạn sẽ được chuyển đến một template cơ bản với hai tùy chọn gói đăng ký và nút mua hàng Apple Pay.
Màn hình đầu tiên liệt kê các gói đăng ký. Màn hình thứ hai và thứ ba là màn hình thanh toán. Mỗi màn hình tương ứng với một gói bạn cung cấp. Nếu bạn chỉ có một gói, hãy xóa màn hình thừa. Nếu có nhiều hơn, bạn cần nhân đôi các màn hình thanh toán.
Màn hình cuối cùng mà người dùng thấy sau khi mua thành công là nơi bạn cần chỉ rõ rằng họ có thể quay lại ứng dụng của bạn.
4. Thiết lập danh sách gói: thêm hoặc xóa các gói và mức giá. Tất cả các mức giá và gói hiển thị trên màn hình không được thêm tự động, vì vậy bạn cần cấu hình thủ công.
5. Thêm hoặc cấu hình màn hình thanh toán cho từng gói bạn có. Chúng tôi khuyến nghị thêm tổng số tiền vào mỗi màn hình thanh toán để người dùng biết họ cần thanh toán bao nhiêu trước khi nhấn nút mua.
6. Trên các màn hình thanh toán, bạn đã có sẵn nút Apple Pay. Để nút này hoạt động, trên mỗi màn hình, hãy cấu hình:
1. **Product type**: Chọn xem bạn muốn thêm thời gian dùng thử hay giảm giá.
2. **Trial period**: Nhập thời gian dùng thử.
3. **Product**: Chọn sản phẩm của bạn từ nhà cung cấp thanh toán.
:::important
Hãy đảm bảo sản phẩm đã được thêm vào Adapty. Nếu không, kết quả mua hàng sẽ được đặt về mặc định.
:::
4. **Subscription discount**: Tùy chọn, chọn một coupon từ nhà cung cấp thanh toán của bạn.
7. Bây giờ, bạn cần liên kết các gói với màn hình thanh toán. Trên màn hình chọn gói, nhấn nút **Continue** và chọn màn hình đích cho từng gói.
Khi bạn đã hoàn thiện paywall, bạn cần lấy link của nó để kích hoạt paywall này trong Adapty. Cách lấy link phụ thuộc vào việc bạn đang kiểm thử hay triển khai lên môi trường production:
1. **Để kiểm thử sandbox**: Nhấn **Preview** ở góc trên bên phải và sao chép link.
2. **Để triển khai production**: Nhấn **Publish** ở góc trên bên phải. Nhấn **Home** và sao chép link từ cột **URL**.
Vậy là xong! Dùng link này để [tiếp tục thiết lập](web-paywall#step-2-trigger-the-paywall).
---
# File: fallback-paywalls
---
---
title: "Fallback paywalls"
description: "Use fallback paywalls to ensure seamless user experience in Adapty."
---
To maintain a fluid user experience, it is important that you set up **fallback versions** for your [paywalls](paywalls) and [onboardings](onboardings).
When your application loads a paywall, the Adapty SDK requests paywall configuration data from our servers. But what if the device cannot connect to Adapty due to network issues or server outages?
* If the user accessed the paywall before, and the device cached its data, the application loads paywall data **from cache**.
* If the device did not cache the paywall, the application looks for a locally stored configuration file. It allows the application to display the paywall without an error.
Adapty automatically generates fallback configuration files for you to download and use. Each file contains platform-specific configurations for *all* your placements.
## Get started
1. [Download the fallback configuration file](/local-fallback-paywalls) from Adapty.
2. Use the Adapty SDK to configure your fallback paywalls:
* [iOS](ios-use-fallback-paywalls)
* [Android](android-use-fallback-paywalls)
* [React Native](react-native-use-fallback-paywalls)
* [Flutter](flutter-use-fallback-paywalls)
* [Unity](unity-use-fallback-paywalls)
* [Kotlin Multiplatform](kmp-use-fallback-paywalls)
* [Capacitor](capacitor-use-fallback-paywalls)
## Limitations
Fallback paywalls are hard-coded and locally stored, so they lack the dynamic capabilities of regular Adapty paywalls.
* Fallback paywalls don't support [internationalization](paywall-localization). When Adapty generates the configuration file, it uses the default `en` locale.
* Each placement can only have one fallback paywall. If your setup inlcudes different paywall configurations for different [audiences](audience), Adapty uses the configuration intended for "All users".
* Fallback paywalls don't support [A/B testing](ab-tests). If a paywall participates in an A/B test, its fallback configuration file will include the variation with the highest weight.
* Fallback paywalls cannot be [managed remotely](customize-paywall-with-remote-config). If you want to update the configuration file, you need to release a new version of the app on App Store / Google Play.
---
# File: local-fallback-paywalls
---
---
title: "Tải xuống paywall dự phòng"
description: "Sử dụng paywall dự phòng cục bộ trong Adapty để đảm bảo luồng đăng ký không bị gián đoạn."
---
Adapty tự động tạo các file cấu hình JSON cho [paywall dự phòng](/fallback-paywalls) của bạn, mỗi nền tảng một file. Các file này cũng chứa dữ liệu dự phòng cho các onboarding của bạn.
Nếu một placement có nhiều hơn một paywall hoặc onboarding, phiên bản dự phòng sẽ bao gồm biến thể có trọng số cao nhất, hoặc đối tượng rộng nhất. Adapty cập nhật các file này bất cứ khi nào bạn chỉnh sửa paywall hoặc onboarding.
Làm theo các bước dưới đây để tải xuống cấu hình dự phòng của bạn:
1. Mở trang **[Placements](https://app.adapty.io/placements)**.
2. Nhấn nút **Fallbacks**.
3. Chọn nền tảng mục tiêu (*iOS* hoặc *Android*) từ menu thả xuống.
4. Chọn phiên bản SDK để bắt đầu tải xuống.
## Sau khi tải xuống \{#after-the-download\}
Làm theo hướng dẫn cài đặt cho nền tảng của bạn:
* [iOS](ios-use-fallback-paywalls)
* [Android](android-use-fallback-paywalls)
* [React Native](react-native-use-fallback-paywalls)
* [Flutter](flutter-use-fallback-paywalls)
* [Unity](unity-use-fallback-paywalls)
* [Kotlin Multiplatform](kmp-use-fallback-paywalls)
* [Capacitor](capacitor-use-fallback-paywalls)
---
# File: paywall-metrics
---
---
title: "Chỉ số paywall"
description: "Theo dõi và phân tích các chỉ số hiệu suất paywall để cải thiện doanh thu gói đăng ký."
---
Adapty thu thập một loạt các chỉ số để giúp bạn đo lường hiệu suất của các paywall tốt hơn. Tất cả các chỉ số được cập nhật theo thời gian thực, ngoại trừ lượt xem được cập nhật vài phút một lần. Tất cả các chỉ số, ngoại trừ lượt xem, được gán cho sản phẩm trong paywall. Tài liệu này mô tả các chỉ số hiện có, định nghĩa và cách tính của chúng.
Các chỉ số paywall được hiển thị trong danh sách paywall, cung cấp cho bạn cái nhìn tổng quan về hiệu suất của tất cả các paywall. Giao diện tổng hợp này trình bày các chỉ số được tổng hợp cho từng paywall, giúp bạn đánh giá hiệu quả và xác định những điểm cần cải thiện.
Để phân tích chi tiết hơn từng paywall, bạn có thể truy cập vào trang chỉ số chi tiết của paywall. Trong phần này, bạn sẽ thấy các chỉ số toàn diện dành riêng cho paywall đã chọn, giúp bạn hiểu sâu hơn về hiệu suất của nó.
### Lọc chỉ số theo ngày cài đặt \{#filter-metrics-by-install-date\}
---
no_index: true
---
Các chỉ số về paywall, trial và mua hàng có thể được nhóm theo hai loại ngày khác nhau:
- **Ngày sự kiện** — khi paywall được xem, trial bắt đầu, hoặc giao dịch mua xảy ra.
- **Ngày cài đặt** — khi người dùng lần đầu mở ứng dụng.
Hai chế độ xem này có thể hiển thị các con số rất khác nhau cho cùng một khoảng thời gian. Hộp kiểm **Filter metrics by install date** kiểm soát chế độ nào được dashboard sử dụng:
- **Bỏ chọn (mặc định)**: Các chỉ số được nhóm theo ngày sự kiện.
- **Đã chọn**: Các chỉ số được nhóm theo ngày cài đặt.
**Ví dụ.** Bạn đặt khoảng thời gian từ ngày 1–30 tháng 4 và xem các trial.
- **Bỏ chọn**: Hiển thị các trial *bắt đầu* trong tháng 4, bất kể người dùng đó cài đặt ứng dụng khi nào.
- **Đã chọn**: Hiển thị các trial từ những người dùng *đã cài đặt* trong tháng 4, bất kể trial của họ bắt đầu khi nào.
Dùng chế độ xem theo ngày cài đặt để đo hiệu quả thu hút người dùng cho một cohort cụ thể. Dùng chế độ xem theo ngày sự kiện để đo hoạt động paywall hoặc onboarding trong một khoảng thời gian cụ thể.
### Điều khiển chỉ số \{#metrics-controls\}
Hệ thống hiển thị các chỉ số dựa trên khoảng thời gian được chọn và sắp xếp chúng theo tham số cột bên trái với ba cấp độ thụt lề.
Đối với paywall đang hoạt động (Live), các chỉ số bao gồm khoảng thời gian từ ngày bắt đầu của paywall đến ngày hiện tại. Đối với các paywall không hoạt động, các chỉ số bao gồm toàn bộ khoảng thời gian từ ngày bắt đầu đến cuối khoảng thời gian được chọn. Các paywall ở trạng thái Draft và Archived được đưa vào bảng chỉ số, nhưng nếu không có dữ liệu cho những paywall đó, chúng sẽ được liệt kê mà không hiển thị chỉ số nào.
#### Tùy chọn hiển thị dữ liệu chỉ số \{#view-options-for-metrics-data\}
Trang paywall cung cấp hai tùy chọn hiển thị dữ liệu chỉ số: theo placement và theo đối tượng.
Trong chế độ xem theo placement, các chỉ số được nhóm theo các placement liên kết với paywall. Điều này cho phép người dùng phân tích chỉ số theo từng placement khác nhau.
Trong chế độ xem theo đối tượng, các chỉ số được nhóm theo đối tượng mục tiêu của paywall. Người dùng có thể đánh giá các chỉ số dành riêng cho từng phân khúc đối tượng khác nhau. Bạn có thể chọn chế độ xem ưa thích bằng tùy chọn dropdown ở đầu trang chi tiết paywall.
#### Khoảng thời gian \{#time-ranges\}
Bạn có thể chọn từ nhiều khoảng thời gian khác nhau để phân tích dữ liệu chỉ số, cho phép bạn tập trung vào các khoảng thời gian cụ thể như ngày, tuần, tháng hoặc phạm vi ngày tùy chỉnh.
#### Bộ lọc và nhóm hiện có \{#available-filters-and-grouping\}
:::link
Bài viết chính: [Điều khiển Analytics](controls-filters-grouping-compare-proceeds)
:::
Adapty cung cấp các công cụ mạnh mẽ để lọc và tùy chỉnh phân tích chỉ số theo nhu cầu của bạn. Với trang chỉ số của Adapty, bạn có quyền truy cập vào nhiều khoảng thời gian, tùy chọn nhóm và khả năng lọc đa dạng.
- Lọc theo: Đối tượng, quốc gia, paywall, trạng thái paywall, nhóm paywall, placement, quốc gia, cửa hàng, sản phẩm và cửa hàng sản phẩm.
- Nhóm theo: Sản phẩm và cửa hàng.
#### Biểu đồ chỉ số đơn \{#single-metrics-chart\}
Một trong những thành phần chính của trang chỉ số paywall là phần biểu đồ, trực quan hóa các chỉ số được chọn và giúp phân tích dễ dàng hơn.
Phần biểu đồ trên trang chỉ số paywall bao gồm biểu đồ thanh ngang trực quan hóa các giá trị chỉ số được chọn. Mỗi thanh trong biểu đồ tương ứng với một giá trị chỉ số và có kích thước tỷ lệ thuận, giúp bạn dễ dàng hiểu dữ liệu ngay từ cái nhìn đầu tiên. Đường ngang biểu thị khung thời gian đang được phân tích, và cột dọc hiển thị các giá trị số của chỉ số. Tổng giá trị của tất cả các chỉ số được hiển thị bên cạnh biểu đồ.
Ngoài ra, nhấp vào biểu tượng mũi tên ở góc trên bên phải của phần biểu đồ sẽ mở rộng chế độ xem, hiển thị các chỉ số đã chọn trên toàn bộ dòng biểu đồ.
#### Tóm tắt tổng chỉ số \{#total-metrics-summary\}
Bên cạnh biểu đồ chỉ số đơn, phần tóm tắt tổng chỉ số được hiển thị, cho thấy các giá trị tích lũy cho các chỉ số đã chọn tại một thời điểm cụ thể, với khả năng thay đổi chỉ số hiển thị bằng menu dropdown.
### Định nghĩa chỉ số \{#metrics-definitions\}
#### Revenue \{#revenue\}
Chỉ số này biểu thị tổng số tiền (tính bằng USD) thu được từ các giao dịch mua và gia hạn. Lưu ý rằng tính toán doanh thu không bao gồm hoa hồng của App Store / Play Store và được tính trước khi trừ bất kỳ khoản phí nào.
#### Proceeds \{#proceeds\}
Chỉ số này biểu thị số tiền thực tế (tính bằng USD) mà chủ ứng dụng nhận được từ các giao dịch mua và gia hạn sau khi trừ hoa hồng áp dụng của App Store / Play Store.
:::important
Hãy thông báo cho Adapty nếu ứng dụng của bạn tham gia chương trình hoa hồng giảm. Để đảm bảo tính toán chính xác, hãy chỉ định trạng thái [Small Business Program](app-store-small-business-program) và [Reduced Service Fee program](google-reduced-service-fee) của bạn trong [cài đặt ứng dụng](general).
:::
Chỉ số này phản ánh doanh thu thuần trực tiếp góp phần vào thu nhập của ứng dụng. Để biết thêm thông tin về cách tính proceeds, bạn có thể tham khảo [tài liệu](analytics-cohorts#revenue-vs-proceeds) của Adapty.
#### ARPPU \{#arppu\}
ARPPU là doanh thu trung bình trên mỗi người dùng trả tiền. Được tính bằng tổng doanh thu chia cho số người dùng trả tiền duy nhất. Ví dụ: $15.000 doanh thu / 1.000 người dùng trả tiền = $15 ARPPU.
#### ARPAS \{#arpas\}
Doanh thu trung bình trên mỗi người đăng ký đang hoạt động cho phép bạn đo lường doanh thu trung bình tạo ra trên mỗi người đăng ký đang hoạt động. Được tính bằng cách chia tổng doanh thu cho số người đăng ký đã kích hoạt bản dùng thử hoặc gói đăng ký. Ví dụ: nếu tổng doanh thu là $5.000 và có 1.000 người đăng ký, ARPAS sẽ là $5. Chỉ số này giúp đánh giá tiềm năng kiếm tiền trung bình trên mỗi người đăng ký.
#### Tỷ lệ chuyển đổi (CR) duy nhất sang giao dịch mua \{#unique-conversion-rate-cr-to-purchases\}
Tỷ lệ chuyển đổi duy nhất sang giao dịch mua được tính bằng cách chia số lượng giao dịch mua cho số lượt xem duy nhất. Ví dụ: nếu có 10 giao dịch mua và 100 lượt xem duy nhất, tỷ lệ chuyển đổi duy nhất sang giao dịch mua sẽ là 10%. Chỉ số này tập trung vào tỷ lệ giao dịch mua trên số lượt xem duy nhất, cung cấp thông tin về hiệu quả chuyển đổi khách truy cập duy nhất thành khách hàng trả tiền.
#### CR to purchases \{#cr-to-purchases\}
Tỷ lệ chuyển đổi sang giao dịch mua được tính bằng cách chia số lượng giao dịch mua cho tổng số lượt xem. Ví dụ: nếu có 10 giao dịch mua và 100 lượt xem, tỷ lệ chuyển đổi sang giao dịch mua sẽ là 10%. Chỉ số này cho biết tỷ lệ phần trăm lượt xem dẫn đến giao dịch mua, cung cấp thông tin về hiệu quả của paywall trong việc chuyển đổi người dùng thành khách hàng trả tiền.
#### Unique CR to trials \{#unique-cr-to-trials\}
Tỷ lệ chuyển đổi duy nhất sang bản dùng thử được tính bằng cách chia số lượng bản dùng thử được bắt đầu cho số lượt xem duy nhất. Ví dụ: nếu có 30 bản dùng thử được bắt đầu và 100 lượt xem duy nhất, tỷ lệ chuyển đổi duy nhất sang bản dùng thử sẽ là 30%. Chỉ số này đo lường tỷ lệ phần trăm lượt xem duy nhất dẫn đến kích hoạt bản dùng thử, cung cấp thông tin về hiệu quả của paywall trong việc chuyển đổi khách truy cập duy nhất thành người dùng dùng thử.
#### Purchases \{#purchases\}
Purchases (Giao dịch mua) biểu thị tổng cộng dồn của các loại giao dịch được thực hiện trên paywall. Các giao dịch sau đây được đưa vào chỉ số này (gia hạn không được tính):
- Giao dịch mua mới được thực hiện trực tiếp trên paywall.
- Chuyển đổi bản dùng thử từ các bản dùng thử được kích hoạt ban đầu trên paywall.
- Hạ cấp, nâng cấp và chuyển đổi ngang cấp gói đăng ký được thực hiện trên paywall.
- Khôi phục gói đăng ký trên paywall, chẳng hạn khi gói đăng ký được tái kích hoạt sau khi hết hạn mà không có tự động gia hạn.
Bằng cách xem xét các loại giao dịch khác nhau này, chỉ số purchases cung cấp cái nhìn toàn diện về hoạt động mua lại và kiếm tiền tổng thể trên paywall của bạn.
#### Trials \{#trials\}
Chỉ số trials biểu thị tổng số bản dùng thử đã được kích hoạt. Nó phản ánh số người dùng đã bắt đầu thời gian dùng thử thông qua paywall của bạn. Chỉ số này giúp theo dõi hiệu quả của ưu đãi dùng thử và cung cấp thông tin về mức độ tương tác của người dùng cũng như tỷ lệ chuyển đổi từ dùng thử sang gói đăng ký trả phí.
#### Trials canceled \{#trials-canceled\}
Chỉ số trials canceled biểu thị số lượng bản dùng thử mà tính năng tự động gia hạn đã bị tắt. Điều này xảy ra khi người dùng tự hủy đăng ký khỏi bản dùng thử, cho thấy quyết định không tiếp tục gói đăng ký sau khi kết thúc thời gian dùng thử. Theo dõi trials canceled cung cấp thông tin có giá trị về hành vi người dùng và giúp bạn hiểu tỷ lệ người dùng từ chối bản dùng thử.
#### Refunds \{#refunds\}
Chỉ số refunds biểu thị số lượng giao dịch mua và gói đăng ký được hoàn tiền. Điều này bao gồm các giao dịch đã bị đảo ngược hoặc hoàn tiền vì nhiều lý do khác nhau, chẳng hạn như yêu cầu của khách hàng, vấn đề thanh toán hoặc bất kỳ chính sách hoàn tiền nào áp dụng.
#### Refund rate \{#refund-rate\}
Tỷ lệ hoàn tiền được tính bằng cách chia số lần hoàn tiền cho số lượng giao dịch mua lần đầu (gia hạn không được tính). Ví dụ: nếu có 5 lần hoàn tiền và 1.000 giao dịch mua lần đầu, tỷ lệ hoàn tiền sẽ là 0,5%.
#### Views \{#views\}
Chỉ số views biểu thị tổng số lần paywall được người dùng xem. Mỗi lần người dùng truy cập paywall được tính là một lượt xem riêng biệt. Ví dụ: nếu một người dùng truy cập paywall hai lần, sẽ được ghi nhận là hai lượt xem. Theo dõi views giúp bạn hiểu mức độ tương tác và tương tác của người dùng với paywall, cung cấp thông tin về hành vi người dùng và hiệu quả của vị trí và thiết kế paywall của bạn.
#### Unique views \{#unique-views\}
Chỉ số unique views biểu thị số lần duy nhất mà paywall được người dùng xem. Không giống như tổng lượt xem tính mỗi lần truy cập là một lượt xem riêng biệt, unique views chỉ tính mỗi lần người dùng truy cập paywall một lần, bất kể họ truy cập bao nhiêu lần. Ví dụ: nếu một người dùng truy cập paywall hai lần, sẽ được ghi nhận là một lượt xem duy nhất. Theo dõi unique views giúp cung cấp thước đo chính xác hơn về mức độ tương tác của người dùng và phạm vi tiếp cận của paywall, vì nó tập trung vào từng người dùng cá nhân thay vì tổng số lượt truy cập.
:::warning
Hãy đảm bảo gửi lượt xem paywall đến Adapty bằng phương thức `.logShowFlow()` (iOS SDK v4+) / `.logShowPaywall()`. Nếu không, lượt xem paywall sẽ không được tính vào chỉ số và tỷ lệ chuyển đổi sẽ không có giá trị tham chiếu.
:::
---
# File: migrate-paywalls
---
---
title: "Migrate paywall giữa các ứng dụng"
description: "Tìm hiểu cách migrate paywall từ các ứng dụng khác trong Adapty."
---
Với Adapty, bạn không cần xây dựng paywall mới từ đầu cho mỗi ứng dụng. Nếu bạn quản lý nhiều ứng dụng, bạn có thể migrate cấu hình paywall builder của bất kỳ paywall nào được tạo bằng builder từ ứng dụng này sang ứng dụng khác.
Migration cho phép bạn sao chép toàn bộ cấu hình giao diện:
- Cài đặt bố cục cho paywall và tất cả các thành phần của paywall
- Media
- Bản địa hóa
Migration chỉ áp dụng cho cấu hình builder và không sao chép sản phẩm hay Remote Config.
:::note
Nếu bạn migrate cấu hình paywall builder có sử dụng font chữ tùy chỉnh, hãy kiểm tra trên thiết bị thực vì chúng có thể hiển thị không đúng.
:::
## Migrate paywall \{#migrate-paywall\}
:::important
Bạn chỉ có thể migrate các paywall được tạo trong Adapty paywall builder **mới**. Để migrate các paywall của **legacy** paywall builder, bạn phải migrate chúng sang paywall builder mới trước.
:::
Để migrate cấu hình paywall builder:
1. **Với paywall mới**: Bắt đầu [tạo paywall](create-paywall) và thêm sản phẩm. Sau đó, nhấp vào **Build no-code paywall** để mở thư viện template.
**Với paywall hiện có**: Truy cập phần **Layout settings** trong tab **Builder & Generator** và nhấp vào **Change template**.
2. Nhấp vào **Choose paywall** trong hộp **Copy a design from your apps** khi đang chỉnh sửa template paywall.
3. Chọn ứng dụng và paywall mà bạn muốn sao chép cấu hình từ đó.
4. Nhấp vào **Copy Selected Paywall**.
Sau khi migration, bạn có thể chỉnh sửa tùy ý mà không ảnh hưởng đến paywall gốc.
---
# File: duplicate-paywalls
---
---
title: "Nhân đôi paywall"
description: "Tìm hiểu cách quản lý các paywall trùng lặp và tối ưu hóa hiệu suất paywall trong Adapty."
---
Nếu bạn cần thực hiện các thay đổi nhỏ đối với một paywall hiện có trong Adapty, đặc biệt khi nó đã được sử dụng trong ứng dụng di động của bạn và bạn không muốn làm ảnh hưởng đến analytics, bạn có thể nhân đôi nó. Sau đó, bạn có thể dùng các bản sao này để thay thế các paywall gốc trong một số hoặc tất cả các placement tùy theo nhu cầu.
Thao tác này tạo ra một bản sao của paywall với tất cả thông tin chi tiết của nó, như tên, sản phẩm và các ưu đãi. Paywall mới sẽ có thêm chữ "Copy" vào tên để bạn có thể dễ dàng phân biệt với bản gốc.
Để nhân đôi một paywall trong Adapty dashboard:
1. Mở mục [**Paywalls**](https://app.adapty.io/paywalls) trong menu chính của Adapty. Trang danh sách paywall trong Adapty dashboard cung cấp tổng quan về tất cả các paywall hiện có trong tài khoản của bạn.
2. Nhấp vào nút **3-dot** bên cạnh paywall và chọn tùy chọn **Duplicate**.
3. Điều chỉnh paywall mới và nhấp vào nút **Save**.
4. Adapty sẽ nhắc bạn thay thế các paywall gốc bằng bản sao trong các placement nếu paywall gốc hiện đang được sử dụng trong bất kỳ placement nào. Nếu bạn chọn **Create and replace original**, các paywall mới sẽ ngay lập tức chuyển sang trạng thái **Live**. Ngoài ra, bạn có thể tạo chúng như các paywall mới ở trạng thái **Draft** và thêm vào các placement sau.
---
# File: archive-paywalls
---
---
title: "Lưu trữ paywall"
description: "Tìm hiểu cách lưu trữ các paywall lỗi thời trong Adapty mà không mất dữ liệu."
---
Khi bạn làm quen với Adapty và tinh chỉnh các cài đặt paywall, bạn có thể tích lũy những paywall không còn phù hợp với chiến lược hoặc chiến dịch hiện tại nữa. Những paywall không dùng đến này, ở trạng thái `Inactive`, có thể làm rối workspace của bạn, khiến việc tìm kiếm những paywall quan trọng trở nên khó khăn hơn. Để giải quyết vấn đề này, Adapty giới thiệu tùy chọn lưu trữ những paywall không cần thiết.
Lưu trữ đảm bảo chúng được lưu giữ an toàn mà không bị xóa vĩnh viễn, sẵn sàng để truy cập khi cần trong tương lai. Ngoài ra, các paywall đã lưu trữ có thể được lọc ra khỏi chế độ xem mặc định, giúp workspace gọn gàng hơn và đơn giản hóa giao diện người dùng. Trong hướng dẫn này, chúng tôi sẽ hướng dẫn bạn cách lưu trữ paywall một cách hiệu quả trong Adapty, giúp bạn kiểm soát tốt hơn quá trình quản lý paywall.
Lưu ý nhỏ: Các paywall đang hoạt động (Live) trong ít nhất một placement không thể được lưu trữ. Nếu bạn muốn lưu trữ một paywall như vậy, hãy xóa nó khỏi tất cả các placement trước.
:::note
Bạn không thể lưu trữ một paywall nếu nó đang được sử dụng trong một A/B test chưa được lưu trữ. Điều này cho phép người dùng xem các chỉ số chi tiết của một A/B test đã hoàn thành, và paywall được liên kết là một phần của dữ liệu đó.
:::
**Để lưu trữ một paywall:**
1. Mở mục [**Paywalls**](https://app.adapty.io/paywalls) trong menu chính của Adapty.
2. Nhấp vào nút **3 chấm** bên cạnh paywall và chọn tùy chọn **Archive**.
3. Khi cửa sổ **Archive paywall** hiện ra, chỉ cần nhập tên của paywall bạn muốn lưu trữ, sau đó nhấp vào nút **Archive**.
---
# File: restore-paywall
---
---
title: "Khôi phục paywall từ lưu trữ"
description: "Khôi phục các paywall trong Adapty để đảm bảo dịch vụ đăng ký không bị gián đoạn cho người dùng."
---
Khả năng lưu trữ paywall là một tính năng rất hữu ích giúp đơn giản hóa quá trình quản lý paywall của bạn. Nó cho phép bạn ẩn các paywall không còn cần thiết, giảm sự lộn xộn trong không gian làm việc. Hơn nữa, tùy chọn khôi phục các paywall đã lưu trữ mang lại sự linh hoạt, cho phép bạn đưa chúng trở lại chiến lược nếu chúng hữu ích trở lại.
Các paywall đã lưu trữ có thể bị lọc ra khỏi chế độ xem mặc định. Để xem chúng, hãy chọn **Archived** trong bộ lọc **State**.
**Để đưa paywall trở lại từ lưu trữ**
1. Mở mục [**Paywalls**](https://app.adapty.io/paywalls) trong menu chính của Adapty.
2. Đảm bảo rằng các paywall đã lưu trữ được hiển thị trong danh sách. Nếu không, hãy cập nhật bộ lọc ở bên phải.
3. Nhấp vào nút **3-dot** bên cạnh paywall đã lưu trữ và chọn **Back to active**.
---
# File: profiles-crm
---
---
title: "Profiles/CRM"
description: "Manage user profiles and CRM data in Adapty to enhance audience segmentation."
---
Profiles is a CRM for your users. With Profiles, you can:
1. Find specific users by profile ID, customer user ID, email, or transaction ID.
2. View the user's event timeline, including billing issues, grace periods, and other [events](events).
3. Analyze user's properties such as subscription state, total revenue/proceeds, and more.
4. Grant the user a subscription.
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
:::note
Các sự kiện từ luồng sự kiện sẽ đến dashboard với một khoảng trễ. Hồ sơ người dùng mới và các thay đổi thuộc tính có thể không hiển thị ngay lập tức.
:::
:::link
To understand how Adapty creates and links user profiles, see [How profiles work](how-profiles-work).
:::
## Finding users
In the Profiles list, you can search for a specific user by:
- **Profile ID**: Adapty's internal identifier for the user (also called Adapty ID).
- **Customer user ID**: Your app's identifier for the user, if you've set one.
- **Email**: The user's email, if sent as a custom attribute.
- **Transaction ID**: The store transaction ID from a purchase.
Click any row to open the user's full profile.
## Subscription state
In the Profiles list, you can filter and sort users by subscription state. The state values are:
| **Trạng thái** người dùng | Mô tả |
| :------------------------ | :----------------------------------------------------------- |
| Subscribed | Người dùng có gói đăng ký đang hoạt động với tính năng tự động gia hạn được bật. |
| Auto-renew off | Người dùng đã tắt tự động gia hạn nhưng vẫn có quyền truy cập các tính năng cao cấp cho đến khi kết thúc kỳ đăng ký. |
| Subscription cancelled | Người dùng đã hủy gói đăng ký và gói đó đã hoàn toàn kết thúc. |
| Billing issue | Người dùng không thể bị tính phí do sự cố thanh toán, xảy ra sau khi gói đăng ký hoặc dùng thử của họ hết hạn. |
| Grace period | Người dùng hiện đang trong thời gian ân hạn do sự cố thanh toán xảy ra khi cố gắng tính phí sau khi gói đăng ký hoặc dùng thử của họ hết hạn. |
| Active trial | Người dùng có gói đăng ký đang hoạt động và hiện đang trong giai đoạn dùng thử. |
| Trial cancelled | Người dùng đã hủy dùng thử và không có gói đăng ký đang hoạt động. |
| Never subscribed | Người dùng chưa bao giờ đăng ký hoặc bắt đầu dùng thử và vẫn là người dùng freemium. |
## User attributes
You can send additional user properties to Adapty using the SDK.
By default, Adapty sets:
| Property | Description |
| ---------------- | ------------------------------------------------------------ |
| Customer user ID | An identifier of your end user in your system. |
| Adapty ID | Internal Adapty identifier of your end user, called Profile ID. |
| IDFA | The Identifier for Advertisers, assigned by Apple to a user's device. Requires App Tracking Transparency (ATT) permission on iOS 14+. Not available on Android. |
| Country | Country of your end user. |
| OS | The operating system used by the end user. |
| Device | The end-user-visible device model name. |
| Install date | The date when the user was first recorded in Adapty:
## Granting a subscription
In a profile, you can extend an active subscription or grant a user lifetime access to an access level — without requiring them to make a purchase.
This is most useful for:
- Compensating a user after a billing or support issue.
- Running manual promotions or beta programs.
- Testing subscription flows without a real purchase.
To grant access, open the user's profile, go to the **Access levels** section, and click **Edit**. Set the expiration date and save. The expiration date must be in the future and cannot be decreased once set. Adjusting it for active subscriptions does not affect ongoing payments.
:::note
Granting access does not create App Store or Google Play purchase events. The user's event feed and analytics will differ from a real purchase flow.
:::
You can also grant access programmatically using the [Grant access level](api-adapty/operations/grantAccessLevel) API method.
## Sharing paid access between user accounts
:::link
Main article: [Sharing paid access between user accounts](sharing-paid-access-between-user-accounts)
:::
### Access sharing history
When access levels are shared or transferred, the user’s profile shows a link to the connected profile — the profile that shared access, or the profile that received it. To view the connected profile, in the user’s **Profile**, click the link next to the access level.
## Next steps
- To understand how Adapty creates and links profiles, see [How profiles work](how-profiles-work).
- To configure the access sharing policy, see [Sharing paid access between user accounts](sharing-paid-access-between-user-accounts).
- To grant access programmatically, see the [Grant access level](api-adapty/operations/grantAccessLevel) API method.
---
# File: how-profiles-work
---
---
title: "Cách hồ sơ người dùng hoạt động"
description: "Hiểu cách Adapty tạo, theo dõi và liên kết hồ sơ người dùng — bao gồm hồ sơ ẩn danh, người dùng đã xác định danh tính, và mối quan hệ parent/inheritor."
---
Mỗi người dùng trong ứng dụng của bạn đều có một hồ sơ người dùng Adapty để theo dõi các giao dịch mua, sự kiện và trạng thái gói đăng ký. Hiểu cách hồ sơ được tạo và liên kết giúp bạn ngăn ngừa lỗi tích hợp, tránh phân mảnh dữ liệu, và diễn giải dữ liệu trong phần [Profiles](profiles-crm).
## Tạo hồ sơ \{#profile-creation\}
Adapty tự động tạo hồ sơ lần đầu tiên người dùng mở ứng dụng của bạn.
**Không có Customer User ID**, hồ sơ là ẩn danh. Một hồ sơ ẩn danh mới được tạo mỗi khi:
- Người dùng cài lại ứng dụng
- Người dùng đăng xuất khỏi ứng dụng của bạn (khi ứng dụng gọi `Adapty.logout()`)
Các giao dịch mua được gắn với lần cài đặt ứng dụng, chứ không phải với danh tính người dùng cố định.
**Với Customer User ID**, hồ sơ tồn tại xuyên suốt các lần cài lại và trên nhiều thiết bị. Sử dụng customer user ID cho phép bạn:
1. Theo dõi người dùng qua các lần cài lại ứng dụng và nhiều thiết bị.
2. Tìm người dùng theo customer user ID của họ trong phần [**Profiles**](profiles-crm).
3. Sử dụng customer user ID trong [server-side API](getting-started-with-server-side-api).
4. Adapty gửi customer user ID đến tất cả các tích hợp.
Hành vi hồ sơ với customer user ID phụ thuộc vào thời điểm bạn thiết lập nó:
- **Khi kích hoạt SDK**: Adapty sử dụng hồ sơ hiện có với customer user ID đó (cho người dùng cũ) hoặc tạo hồ sơ mới (cho người dùng lần đầu).
- **Sau khi kích hoạt SDK**: Adapty tạo hồ sơ ẩn danh khi kích hoạt. Khi bạn xác định danh tính người dùng sau đó, Adapty liên kết customer user ID với hồ sơ ẩn danh (cho người dùng lần đầu) hoặc chuyển sang hồ sơ hiện có với ID đó (cho người dùng cũ).
**Nên dùng cách nào:**
- **Customer user ID có sẵn khi khởi động ứng dụng** (ví dụ: lưu từ phiên trước) — truyền vào `activate()` khi khởi tạo SDK.
- **Người dùng đăng nhập sau khi ứng dụng khởi động** — gọi `identify()` sau khi xác thực. Adapty liên kết ID với hồ sơ hiện tại (nếu ID mới) hoặc chuyển sang hồ sơ hiện có (nếu ID đã tồn tại).
- **Người dùng có thể mua trước khi đăng nhập** — gọi `identify()` sau khi đăng nhập. Nếu customer user ID đã tồn tại trong Adapty, hãy lấy lại hồ sơ sau đó để đồng bộ mức độ truy cập hiện tại.
Để biết chi tiết triển khai, xem hướng dẫn SDK về [xác định danh tính người dùng](identifying-users).
:::note
Nếu người dùng cũ trước đây đã dùng ứng dụng của bạn mà không có customer user ID, những hồ sơ ẩn danh đó sẽ không được tự động gộp khi bạn bắt đầu xác định danh tính tại thời điểm kích hoạt SDK. Để duy trì lịch sử đầy đủ cho những người dùng như vậy, hãy dùng `identify()` sau khi đăng nhập thay thế.
:::
## Hồ sơ parent và inheritor \{#parent-and-inheritor-profiles\}
Khi cùng một gói đăng ký phía cửa hàng được liên kết với nhiều hơn một hồ sơ người dùng Adapty, Adapty coi các hồ sơ đó là một chuỗi: một hồ sơ **parent** và một hoặc nhiều hồ sơ **inheritor** chia sẻ quyền truy cập từ cùng một giao dịch mua.
Điều này xảy ra khi:
- [Chia sẻ quyền truy cập có trả phí giữa các tài khoản người dùng](sharing-paid-access-between-user-accounts) được bật và người dùng đăng nhập trên thiết bị nơi một hồ sơ khác đã thực hiện giao dịch mua trước đó.
- Người dùng cài lại ứng dụng mà không có `customer_user_id`, và hồ sơ mới tiếp nhận giao dịch mua từ lần cài đặt trước.
- Các người dùng đã xác định danh tính khác nhau khôi phục giao dịch mua trên cùng một thiết bị.
- Ứng dụng được chuyển giữa các Apple Team ID và ứng dụng mới tiếp nhận các giao dịch mua thực hiện dưới Team ID cũ.
**Cách chọn parent.**
Parent là **hồ sơ đầu tiên ghi nhận giao dịch mua** — được xác định theo thứ tự receipt mua trong Adapty, không phải theo thứ tự tạo hồ sơ. Ví dụ: bạn cài ứng dụng và không mua gì, sau đó cài lại và mua gói đăng ký. Hồ sơ thứ hai trở thành parent vì nó thực hiện giao dịch mua. Hồ sơ đầu tiên trở thành inheritor và được cấp quyền truy cập thông qua tính năng chia sẻ.
**Cách phân phối sự kiện:**
- **Sự kiện giao dịch** (mua hàng, gia hạn, hủy, vấn đề thanh toán, thời gian ân hạn, hoàn tiền): Chỉ xuất hiện trên hồ sơ **parent** đã thực hiện giao dịch mua. Tất cả các lần gia hạn và cập nhật gói đăng ký tiếp tục xuất hiện trên hồ sơ này.
- **Sự kiện `access_level_updated`**: Xuất hiện trên **cả hồ sơ parent lẫn inheritor** bất cứ khi nào trạng thái mức độ truy cập thay đổi. Điều này giúp tất cả các hồ sơ được kết nối luôn cập nhật về trạng thái truy cập hiện tại.
Hồ sơ parent hiển thị toàn bộ lịch sử giao dịch. Các hồ sơ inheritor chỉ hiển thị các cập nhật mức độ truy cập của họ và một liên kết đến hồ sơ parent trong phần **Access level**.
**Theo dõi cùng một gói đăng ký trên nhiều hồ sơ.**
Mỗi hồ sơ inheritor có `profile_id` riêng, vì vậy `profile_id` không ổn định xuyên suốt một chuỗi. Để xác định cùng một gói đăng ký trên nhiều hồ sơ — ví dụ, khi đối chiếu các sự kiện webhook hoặc khớp hồ sơ dashboard với một người dùng cơ bản — hãy dùng mã định danh phía cửa hàng thay thế.
| Trường | Dùng để |
| --- | --- |
| `store_original_transaction_id` | Xác định chuỗi gói đăng ký trên nhiều hồ sơ. Duy nhất cho mỗi gói đăng ký Apple. |
| `profiles_sharing_access_level` (trường webhook) | Tất cả các hồ sơ hiện đang được cấp quyền bởi gói đăng ký, khi tính năng chia sẻ được bật. |
| `profile_id` | **Không** phù hợp để theo dõi xuyên hồ sơ — mỗi inheritor có `profile_id` riêng. |
## Giao dịch không có hồ sơ \{#transactions-without-profiles\}
Một số giao dịch trong Adapty không được gắn với hồ sơ nào — chúng xuất hiện trong phân tích và xuất dữ liệu nhưng không xuất hiện trong danh sách Profiles. Điều này xảy ra với **các thông báo cửa hàng server-to-server (S2S)** được gửi cho những người dùng mà tài khoản của họ chưa bao giờ kết nối với ứng dụng của bạn thông qua Adapty SDK. Các nguồn đã biết bao gồm:
- Thông báo S2S của App Store (bao gồm sự kiện hoàn tiền)
- Thông báo S2S của Google Play
- Sự kiện webhook của Stripe và Paddle
Các giao dịch này:
- **Xuất hiện trong biểu đồ phân tích** (chúng được tính vào tổng chỉ số)
- **Xuất hiện trong xuất dữ liệu** (S3, GCS, BigQuery) với `profile_id` được đặt là `null`
- **Không xuất hiện trong danh sách Profiles** — không có hồ sơ nào để gắn vào
Nếu bạn thấy nhiều sự kiện hơn trong phân tích hoặc xuất dữ liệu so với những gì bạn có thể tìm thấy trong giao diện Profiles, sự khác biệt đó có thể là do các giao dịch không có hồ sơ này. Để tìm chúng trong một bản xuất, hãy lọc các hàng có `profile_id IS NULL`.
## Chia sẻ quyền truy cập có trả phí giữa các tài khoản người dùng \{#sharing-paid-access-between-user-accounts\}
:::link
Bài viết chính: [Chia sẻ quyền truy cập có trả phí giữa các tài khoản người dùng](sharing-paid-access-between-user-accounts)
:::
Để thiết lập chính sách chia sẻ mức độ truy cập, trên trang cài đặt [**General**](general), hãy chọn một tùy chọn chia sẻ. Bạn có thể đặt chính sách riêng cho [môi trường sandbox](test-purchases-in-sandbox).
---
no_index: true
---
**Enabled (mặc định)**
Người dùng đã xác định (những người có [Customer User ID](identifying-users#set-customer-user-id-on-configuration)) có thể chia sẻ cùng một [mức độ truy cập](access-level) do Adapty cung cấp nếu thiết bị của họ đăng nhập vào cùng một Apple/Google ID. Điều này hữu ích khi người dùng cài lại ứng dụng và đăng nhập bằng email khác — họ vẫn có thể truy cập vào giao dịch mua trước đó. Với tùy chọn này, nhiều người dùng đã xác định có thể dùng chung một mức độ truy cập.
Dù mức độ truy cập được chia sẻ, tất cả các giao dịch trong quá khứ và tương lai vẫn được ghi lại dưới dạng sự kiện trong Customer User ID gốc để đảm bảo tính nhất quán của dữ liệu phân tích và lưu giữ toàn bộ lịch sử giao dịch — bao gồm thời gian dùng thử, mua gói đăng ký, gia hạn, v.v., đều được liên kết với cùng một hồ sơ người dùng.
**Transfer access to new user**
Người dùng đã xác định vẫn có thể tiếp tục truy cập [mức độ truy cập](access-level) do Adapty cung cấp, ngay cả khi họ đăng nhập bằng [Customer User ID](identifying-users#set-customer-user-id-on-configuration) khác hoặc cài lại ứng dụng, miễn là thiết bị đăng nhập vào cùng một Apple/Google ID.
Khác với tùy chọn trước, Adapty sẽ chuyển giao dịch mua giữa các người dùng đã xác định. Điều này đảm bảo nội dung đã mua vẫn khả dụng, nhưng chỉ một người dùng có thể truy cập tại một thời điểm. Ví dụ: nếu UserA mua gói đăng ký và UserB đăng nhập trên cùng thiết bị đó và khôi phục giao dịch, UserB sẽ được cấp quyền truy cập gói đăng ký đó, còn UserA sẽ bị thu hồi.
Nếu một trong hai người dùng (mới hoặc cũ) chưa được xác định, mức độ truy cập vẫn sẽ được chia sẻ giữa các hồ sơ người dùng đó trong Adapty.
Dù mức độ truy cập được chuyển giao, tất cả các giao dịch trong quá khứ và tương lai vẫn được ghi lại dưới dạng sự kiện trong Customer User ID gốc để đảm bảo tính nhất quán của dữ liệu phân tích và lưu giữ toàn bộ lịch sử giao dịch — bao gồm thời gian dùng thử, mua gói đăng ký, gia hạn, v.v., đều được liên kết với cùng một hồ sơ người dùng.
Sau khi chuyển sang **Transfer access to new user**, mức độ truy cập sẽ không được chuyển ngay lập tức giữa các hồ sơ người dùng. Quá trình chuyển giao cho từng mức độ truy cập cụ thể chỉ được kích hoạt khi Adapty nhận được sự kiện từ cửa hàng, chẳng hạn như gia hạn gói đăng ký, khôi phục, hoặc khi xác thực giao dịch.
**Disabled**
Hồ sơ người dùng đã xác định đầu tiên được cấp mức độ truy cập sẽ giữ nó mãi mãi. Đây là lựa chọn tốt nhất nếu logic nghiệp vụ của bạn yêu cầu giao dịch mua phải được gắn với một Customer User ID duy nhất.
Lưu ý rằng mức độ truy cập vẫn được chia sẻ giữa các người dùng ẩn danh.
Bạn có thể "gỡ liên kết" giao dịch mua bằng cách [xóa hồ sơ người dùng của chủ sở hữu](https://adapty.io/docs/vi/api-adapty/operations/deleteProfile). Sau khi xóa, mức độ truy cập sẽ khả dụng cho hồ sơ người dùng đầu tiên yêu cầu nó, dù là ẩn danh hay đã xác định.
Việc tắt chia sẻ chỉ ảnh hưởng đến người dùng mới. Các gói đăng ký đã được chia sẻ giữa người dùng sẽ tiếp tục được chia sẻ ngay cả sau khi tắt tùy chọn này.
:::warning
Apple và Google yêu cầu in-app purchase phải được chia sẻ hoặc chuyển giao giữa các người dùng vì họ dựa vào Apple/Google ID để liên kết giao dịch mua. Nếu không có chia sẻ, việc khôi phục giao dịch mua có thể không hoạt động sau khi cài lại ứng dụng.
Tắt chia sẻ có thể khiến người dùng không thể lấy lại quyền truy cập sau khi đăng nhập.
Chúng tôi khuyến nghị chỉ tắt chia sẻ nếu người dùng của bạn **bắt buộc phải đăng nhập** trước khi thực hiện giao dịch mua. Nếu không, một người dùng đã xác định có thể mua gói đăng ký, đăng nhập vào tài khoản khác và mất quyền truy cập vĩnh viễn.
:::
### Tôi nên chọn cài đặt nào? \{#which-setting-should-i-choose\}
| Ứng dụng của tôi... | Tùy chọn nên chọn |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| Không có hệ thống đăng nhập và chỉ sử dụng ID hồ sơ người dùng ẩn danh của Adapty. | Dùng tùy chọn mặc định, vì mức độ truy cập luôn được chia sẻ giữa các ID hồ sơ người dùng ẩn danh cho cả ba tùy chọn. |
| Có hệ thống đăng nhập tùy chọn và cho phép khách hàng mua trước khi tạo tài khoản. | Chọn **Transfer access to new user** để đảm bảo những khách hàng mua khi chưa có tài khoản vẫn có thể khôi phục giao dịch sau này. |
| Yêu cầu khách hàng tạo tài khoản trước khi mua, nhưng cho phép giao dịch mua được liên kết với nhiều Customer User ID. | Chọn **Transfer access to new user** để đảm bảo chỉ một Customer User ID có quyền truy cập tại một thời điểm, đồng thời vẫn cho phép người dùng đăng nhập bằng Customer User ID khác mà không mất quyền truy cập đã trả phí. |
| Yêu cầu khách hàng tạo tài khoản trước khi mua, với quy tắc nghiêm ngặt ràng buộc giao dịch mua với một Customer User ID duy nhất. | Chọn **Disabled** để đảm bảo giao dịch không bao giờ được chuyển giao giữa các tài khoản. |
## Dấu thời gian sự kiện có ngày trong tương lai (Apple/iOS) \{#event-timestamps-with-future-dates-appleios\}
Hành vi này chỉ xảy ra với Apple App Store. Hệ thống thông báo của Google Play không gửi sự kiện trước.
Dấu thời gian sự kiện trong hồ sơ và tích hợp có thể hiển thị ngày trong tương lai vì Apple gửi sự kiện gia hạn trước.
- **Lý do xảy ra**: Apple làm điều này để đảm bảo gói đăng ký gia hạn tự động trước khi hết hạn, ngăn ngừa gián đoạn dịch vụ cho người dùng. Để biết thêm chi tiết, xem Diễn đàn nhà phát triển Apple: [Server Notifications for Subscriptions](https://developer.apple.com/forums/tags/app-store-server-notifications).
- **Các loại sự kiện bị ảnh hưởng**: Thông thường, điều này áp dụng cho các lần gia hạn gói đăng ký và chuyển đổi từ dùng thử sang trả phí. Các sự kiện này có thể có dấu thời gian trong tương lai vì Apple thông báo cho hệ thống về chúng trước.
- **Các loại sự kiện khác**: Các in-app purchase bổ sung và thay đổi gói đăng ký được ghi lại với dấu thời gian thực tế vì những sự kiện này không thể được dự đoán trước.
- **Ảnh hưởng đến Analytics và Event Feed**: Các sự kiện này sẽ chỉ xuất hiện trong **Analytics** và **Event Feed** sau khi dấu thời gian của chúng đã qua. Các sự kiện có dấu thời gian trong tương lai không hiển thị trong cả hai phần.
- **Ảnh hưởng đến Integrations**: Adapty gửi sự kiện đến các tích hợp ngay khi nhận được. Nếu một sự kiện có dấu thời gian trong tương lai, Adapty gửi nó đến tích hợp của bạn với dấu thời gian trong tương lai không thay đổi.
## Bước tiếp theo \{#next-steps\}
- Để dùng dashboard Profiles để tìm và quản lý người dùng, xem [Profiles](profiles-crm).
- Để thiết lập xác định danh tính người dùng trong ứng dụng, xem hướng dẫn SDK về [xác định danh tính người dùng](identifying-users).
- Để cấu hình chính sách chia sẻ quyền truy cập, xem [Chia sẻ quyền truy cập có trả phí giữa các tài khoản người dùng](sharing-paid-access-between-user-accounts).
---
# File: sharing-paid-access-between-user-accounts
---
---
title: "Chia sẻ quyền truy cập trả phí giữa các tài khoản người dùng"
description: "Chia sẻ quyền truy cập trả phí giữa các tài khoản người dùng khác nhau để hỗ trợ người dùng có nhiều thiết bị hoặc nhiều hồ sơ trong ứng dụng"
---
Khi người dùng thực hiện một giao dịch mua, Adapty sẽ gán một [mức độ truy cập](access-level) mới cho [hồ sơ người dùng](identifying-users) đang hoạt động của họ. Mức độ truy cập này cho phép người mua truy cập vào nội dung trả phí.
Hồ sơ người dùng của người mua có thể thay đổi ngoài ý muốn nếu họ cài đặt lại ứng dụng, hoặc đăng nhập vào một tài khoản trong ứng dụng mới. Để đảm bảo quyền truy cập liên tục, Adapty tự động chia sẻ mức độ truy cập của người dùng giữa hồ sơ ban đầu và các hồ sơ tiếp theo.
Cách tiếp cận này phù hợp với hầu hết các ứng dụng. Nhưng nếu logic nghiệp vụ của bạn yêu cầu, bạn có thể chọn chính sách chia sẻ quyền truy cập trả phí hạn chế hơn.
Mở trang [General Settings](https://app.adapty.io/settings/general) để thiết lập chính sách chia sẻ mức độ truy cập. Để thuận tiện cho việc kiểm thử, bạn có thể thay đổi cài đặt này chỉ cho [môi trường sandbox](#sharing-paid-access-on-sandbox).
## Enabled (default) \{#enabled-default\}
Cài đặt này phù hợp nhất cho các ứng dụng **không có xác thực tích hợp**. Sau khi mua, tất cả các hồ sơ được liên kết với cùng một tài khoản cửa hàng sẽ tự động *kế thừa* mức độ truy cập.
* Nếu người dùng đăng nhập vào ứng dụng của bạn với thông tin xác thực mới, họ vẫn giữ quyền truy cập vào nội dung trả phí.
* Nếu người dùng cài đặt lại ứng dụng sau khi khôi phục cài đặt gốc, họ vẫn giữ quyền truy cập vào nội dung trả phí.
* Nếu người dùng cài đặt ứng dụng trên các thiết bị khác với cùng một tài khoản cửa hàng, giao dịch mua sẽ được cung cấp trên tất cả các thiết bị. Ngay cả khi mỗi phiên bản ứng dụng có hồ sơ khách hàng riêng.
## Transfer access to new user \{#transfer-access-to-new-user\}
Cài đặt này phù hợp nhất cho các ứng dụng cho phép mua hàng **có hoặc không có xác thực**, hoặc muốn thực thi chính sách **một thiết bị mỗi người dùng**.
Adapty giới hạn quyền truy cập mua hàng cho 1 customer ID tại một thời điểm. Chủ thiết bị có thể cài đặt lại ứng dụng, đăng nhập và đăng xuất, nhưng không thể truy cập cùng một sản phẩm từ nhiều hơn một customer ID đồng thời.
Khi bật cài đặt này, các hồ sơ ẩn danh (ví dụ: hồ sơ trở nên hoạt động sau khi người dùng đăng xuất) luôn kế thừa mức độ truy cập của customer ID đang hoạt động gần nhất. Điều này là cần thiết để ngăn mất quyền truy cập sau này.
:::warning
Khi bạn tắt cài đặt mặc định và bật **Transfer access to new user**, Adapty không cập nhật ngay lập tức mức độ truy cập của các hồ sơ khách hàng hiện có.
Việc chuyển đổi xảy ra khi người dùng kích hoạt một sự kiện cửa hàng mới: ví dụ, gia hạn gói đăng ký hoặc khôi phục giao dịch mua của họ.
:::
:::important
Adapty thu hồi hồ sơ cũ chỉ khi hồ sơ mới có [Customer User ID](identifying-users#set-customer-user-id-on-configuration) tại thời điểm SDK truyền giao dịch. Nếu `restorePurchases` chạy trên một hồ sơ ẩn danh, cả Customer User ID cũ và hồ sơ ẩn danh mới đều sẽ có mức độ truy cập. Hồ sơ cũ sẽ bị thu hồi sau, khi bạn xác định hồ sơ ẩn danh.
Để tránh điều này, hãy gọi các phương thức SDK theo thứ tự: `activate` → `identify` → `restorePurchases`.
:::
## Tắt chia sẻ quyền truy cập trả phí \{#disable-paid-access-sharing\}
Cài đặt này **chỉ phù hợp** cho các ứng dụng có **xác thực bắt buộc** hoặc triển khai quản lý quyền truy cập độc lập. Trong các trường hợp khác, người dùng có thể không thể truy cập các giao dịch mua của họ, và ứng dụng của bạn có nguy cơ **không vượt qua được quy trình xét duyệt bắt buộc của cửa hàng**.
Nếu bạn tắt chia sẻ quyền truy cập trả phí, Adapty sẽ gắn sản phẩm với [customer ID](identifying-users#set-customer-user-id-on-configuration) đang hoạt động tại thời điểm mua, và không chia sẻ mức độ truy cập với bất kỳ hồ sơ khách hàng nào khác. Chính sách này cho phép phân phối sản phẩm theo tỷ lệ 1-1 nghiêm ngặt.
:::warning
Khi bạn tắt chia sẻ quyền truy cập trả phí, bạn ngăn các customer ID kế thừa quyền truy cập trả phí. Nếu một customer ID đã kế thừa quyền truy cập trả phí trong quá khứ, nó không thể bị thu hồi tự động.
:::
:::important
Trong các tình huống khẩn cấp, bạn có thể cần [xóa hồ sơ người dùng](api-adapty/operations/deleteProfile) để hồ sơ tiếp theo có sẵn (dù đã được xác định hay ẩn danh) có thể xác nhận quyền sở hữu mức độ truy cập của nó.
:::
## Tham khảo thực tế \{#practical-reference\}
Sau khi bạn chọn một chế độ, các mô tả dưới đây cho biết điều gì sẽ xảy ra: hồ sơ nào thấy quyền truy cập, khi nào hồ sơ cũ mất quyền truy cập, và các sự kiện webhook nào được kích hoạt.
| Chế độ | Nhiều hồ sơ chia sẻ một giao dịch mua? | Hồ sơ cũ bị thu hồi khi chuyển? | Khi nào hồ sơ cũ bị thu hồi | Sự kiện webhook khi hồ sơ thứ hai xác nhận gói đăng ký |
| --- | --- | --- | --- | --- |
| **Enabled (default)** | Có — mọi hồ sơ khôi phục hoặc đăng nhập đều kế thừa quyền truy cập | Không bao giờ | N/A | `access_level_updated` (`is_active=true`) cho mỗi hồ sơ mới kế thừa |
| **Transfer access to new user** | Không — độc quyền, nhưng có thể chuyển giữa các hồ sơ | Có | Ngay lập tức khi thiết bị được xác định mới truyền giao dịch (`restorePurchases`, identify, hoặc sự kiện phía cửa hàng tiếp theo) | Hồ sơ mới: `access_level_updated` (`is_active=true`). Hồ sơ cũ: `access_level_updated` (`is_active=false`) |
| **Disabled** | Không — một Customer User ID cho mỗi giao dịch mua, vĩnh viễn | N/A — quyền truy cập không bao giờ được chuyển | N/A | Không có sự kiện nào trên hồ sơ thứ hai. SDK không hiển thị quyền truy cập cho hồ sơ đó |
## Chia sẻ quyền truy cập trả phí trên sandbox \{#sharing-paid-access-on-sandbox\}
Bạn có thể thiết lập chính sách chia sẻ quyền truy cập trả phí riêng cho môi trường sandbox. Khi bạn kiểm thử giao dịch mua trong môi trường sandbox, hãy lưu ý các hành vi sau:
* Apple lưu trữ thông tin về các giao dịch mua trong quá khứ của bạn trong lịch sử mua của tài khoản. Adapty SDK cũng có thể truy cập thông tin này.
* Nếu bạn cài đặt lại ứng dụng và Adapty phát hiện rằng sản phẩm đã được mua trước đó, hồ sơ đang hoạt động sẽ kế thừa mức độ truy cập.
* Nếu Apple phát hiện một giao dịch mua hiện có cho sản phẩm, Apple sẽ không cho phép bạn mua cùng một sản phẩm hai lần, ngay cả khi hồ sơ đang hoạt động không có mức độ truy cập cần thiết.
Hành vi này xảy ra **độc lập với cài đặt chia sẻ quyền truy cập trả phí của bạn**. Ứng dụng của bạn không hiển thị paywall, bạn không thể mua sản phẩm. Giải pháp duy nhất là **xóa lịch sử mua của tài khoản**. Hãy xem [hướng dẫn kiểm thử sandbox](test-purchases-in-sandbox) để biết hướng dẫn chi tiết.
:::warning
Các gói đăng ký sandbox trên Apple tự động gia hạn mỗi vài phút. Những lần gia hạn nhanh chóng này có thể thay đổi hồ sơ mà Adapty coi là [cha](how-profiles-work#parent-and-inheritor-profiles) — một mô hình chuỗi mà môi trường sản xuất hiếm khi tái hiện. Hãy kiểm thử chế độ bạn sử dụng trong môi trường sản xuất và xác nhận hành vi với Apple ID thực trước khi rút ra kết luận từ sandbox.
:::
## Chia sẻ quyền truy cập trả phí trong analytics \{#paid-access-sharing-in-analytics\}
* Adapty ghi lại các giao dịch khi chúng xảy ra. Một giao dịch có thể được liên kết với nhiều hơn một hồ sơ, nhưng không được tính nhiều hơn một lần.
* Nếu hai hoặc nhiều hồ sơ chia sẻ cùng một mức độ truy cập, giao dịch mua được quy cho [hồ sơ cha](how-profiles-work#parent-and-inheritor-profiles).
* Việc kế thừa mức độ truy cập không ảnh hưởng đến thống kê cài đặt. Để xác định cách Adapty đếm lượt cài đặt, bạn có thể chọn một trong hai [định nghĩa cài đặt](installs#counting-modes) có sẵn trên trang cài đặt.
---
# File: segments
---
---
title: "Phân khúc"
description: "Tạo và quản lý phân khúc người dùng để nhắm mục tiêu tốt hơn trong Adapty."
---
**Phân khúc** là tập hợp các bộ lọc giúp nhóm người dùng có đặc điểm chung. Dùng phân khúc để nhắm mục tiêu paywall và A/B test hiệu quả hơn.
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
:::note
Các sự kiện từ luồng sự kiện sẽ đến dashboard với một khoảng trễ. Hồ sơ người dùng mới và các thay đổi thuộc tính có thể không hiển thị ngay lập tức.
:::
Sau khi tạo phân khúc, bạn có thể [sử dụng nó làm **đối tượng** trong Placements và A/B test](audience) để kiểm soát paywall mà người dùng thấy (một hoặc nhiều). Ví dụ:
- Hiển thị paywall tiêu chuẩn cho người chưa đăng ký và cung cấp ưu đãi cho người đã hủy gói đăng ký hoặc dùng thử trước đó.
- Hiển thị các paywall khác nhau cho người dùng từ các quốc gia khác nhau.
- Nhắm mục tiêu người dùng dựa trên dữ liệu attribution từ Apple Search Ads.
- Đảm bảo người dùng phiên bản cũ vẫn thấy paywall hiện tại, trong khi phiên bản mới hơn nhận được phiên bản cập nhật.
- [Trong Analytics](controls-filters-grouping-compare-proceeds#filter-and-group-data), lọc theo phân khúc để xem hiệu suất của các nhóm người dùng cụ thể. Nhóm theo phân khúc để so sánh hiệu suất hoặc mức đóng góp trong **Tất cả người dùng**.
## Tạo phân khúc \{#creation\}
Để tạo phân khúc, nhập tên và chọn các thuộc tính định nghĩa bộ lọc của nó. Khi bạn chọn nhiều thuộc tính, người dùng phải thỏa mãn tất cả các điều kiện. Adapty áp dụng logic AND giữa các thuộc tính.
## Các thuộc tính có sẵn \{#available-attributes\}
:::note
Nhiều thuộc tính người dùng được đặt tự động (như **Country** hoặc **Calculated total revenue USD**), nhưng **Age**, **App user ID**, dữ liệu **Attribution**, **Gender** và **Custom attributes** thì không. Bạn phải [đặt thuộc tính người dùng](setting-user-attributes) hoặc [truyền dữ liệu attribution](attribution-integration) nếu muốn dùng chúng để phân khúc.
:::
:::tip
Đối với các thuộc tính theo ngày, bạn có thể lọc bằng:
- **Ngày cố định**: Chọn ngày cụ thể từ lịch (ví dụ: hiển thị ưu đãi đặc biệt cho người dùng cài đặt trong khoảng Black Friday đến Cyber Monday)
- **Khoảng thời gian tương đối**: Đặt khoảng thời gian động như "7 ngày qua" hoặc "3 tháng qua" (ví dụ: thu hút lại người dùng chưa mở app từ 30+ ngày trước, hoặc nhắm mục tiêu người cài đặt gần đây)
Khoảng thời gian tương đối tự động cập nhật, rất lý tưởng cho các chiến dịch liên tục. Ngày cố định phù hợp nhất cho các chương trình khuyến mãi có thời hạn.
:::
| Thuộc tính | Lọc theo |
|------------|----------|
| **Age** | Độ tuổi của người dùng. Lưu ý rằng độ tuổi được tính khi Adapty nhận được lần đầu tiên và không được cập nhật sau đó. |
| **App User ID** | Định danh của người dùng trong ứng dụng của bạn ([customer_user_id](profiles-crm#user-attributes)). Bạn có thể lọc theo sự hiện diện hoặc vắng mặt của nó, ví dụ để chỉ hiển thị paywall cho những người chưa đăng nhập. |
| **App version (current)** | Phiên bản hiện tại của ứng dụng được cài đặt trên thiết bị của người dùng mà Adapty nhận dữ liệu sự kiện gần nhất — **được cập nhật khi người dùng nâng cấp**, vì vậy nó luôn phản ánh phiên bản họ đang dùng. Sử dụng thuộc tính này khi triển khai cho tất cả người dùng đang chạy một phiên bản cụ thể, kể cả những người đã nâng cấp lên từ phiên bản cũ hơn. Khi tạo phân khúc, chọn biểu tượng bút chì bên cạnh **App version** và thêm phiên bản mới để sử dụng ngay.
| Trường | Mô tả |
| ------ |-------|
| **Name** | Nhãn cho thuộc tính tùy chỉnh, chỉ dùng trong Adapty Dashboard. |
| **Key** | Định danh duy nhất cho thuộc tính. Phải khớp với key được dùng trong SDK. |
| **Type** | Chọn giữa:
## Nhân bản phân khúc \{#duplicate-segments\}
Nếu bạn cần một phân khúc tương tự phân khúc đã có, hãy nhân bản nó thay vì xây dựng lại từ đầu. Điều này giúp tiết kiệm thời gian cho các nhóm chạy nhiều chiến dịch hoặc A/B test với các nhóm người dùng chồng lên nhau.
Nhân bản phân khúc tạo ra một bản sao với tất cả các bộ lọc và mô tả của nó. Phân khúc mới sẽ có thêm "(copy)" vào tên để phân biệt với bản gốc. Phân khúc mới độc lập với bản gốc. Thay đổi ở một phân khúc không ảnh hưởng đến phân khúc kia.
Để nhân bản phân khúc trong Adapty Dashboard:
1. Mở phần **Profiles & Segments** trong menu chính của Adapty và chuyển sang tab [**Segments**](https://app.adapty.io/segments).
2. Nhấp vào nút **3 chấm** bên cạnh phân khúc và chọn **Duplicate**.
3. Mở phân khúc mới và điều chỉnh bộ lọc theo nhu cầu.
## Xóa phân khúc \{#delete-segments\}
Khi bạn không còn cần một phân khúc, bạn có thể xóa nó vĩnh viễn.
Adapty chặn việc xóa nếu phân khúc đang được dùng làm đối tượng bởi một trong những trường hợp sau:
- **Một placement**: Ít nhất một placement chưa bị xóa đang dùng phân khúc này làm đối tượng.
- **Một A/B test (đang chạy hoặc đã hoàn thành)**: Ít nhất một A/B test chưa bị xóa đang dùng phân khúc này làm đối tượng.
Đối với việc xóa phân khúc, Adapty coi cả A/B test **đang chạy** lẫn **đã hoàn thành** là đang hoạt động. Một A/B test đã hoàn thành vẫn dùng đối tượng để hiển thị paywall hoặc onboarding sau test cho người dùng phù hợp, và các chỉ số lịch sử của test được giới hạn trong phân khúc đó. Phân khúc chỉ được giải phóng khi bản thân A/B test bị xóa.
:::warning
Việc xóa phân khúc là vĩnh viễn. Phân khúc không thể được khôi phục.
:::
Để xóa phân khúc trong Adapty Dashboard:
1. Vào **Profiles & Segments** trong menu chính của Adapty và chuyển sang tab [**Segments**](https://app.adapty.io/segments).
2. Nhấp vào nút **3 chấm** bên cạnh phân khúc và chọn **Delete**.
3. Nhập tên phân khúc vào trường xác nhận, sau đó nhấp **Delete forever**.
:::info
Nếu phân khúc đang được sử dụng, hộp thoại sẽ liệt kê các placement và A/B test đang tham chiếu đến nó.
Để mở khóa việc xóa, mở từng placement hoặc A/B test trong danh sách và xóa phân khúc khỏi đối tượng của nó hoặc xóa hoàn toàn placement hay A/B test đó. Khi không còn gì tham chiếu đến phân khúc, bạn có thể xóa nó.
:::
---
# File: event-feed
---
---
title: "Event feed"
description: "Theo dõi và phân tích hoạt động người dùng với event feed của Adapty."
---
Event feed cho phép bạn theo dõi trực quan các [Sự kiện (Events)](events) được tạo bởi Adapty và kiểm tra trạng thái xuất dữ liệu sang các tích hợp bên thứ ba, bao gồm cả webhook.
:::warning
Event Feed không hiển thị:
- **Giao dịch từ Server-side API v1**: Được tạo bằng [server-side API (phiên bản 1)](server-side-api-specs-legacy#requests). Hãy dùng [server-side API (phiên bản 2)](api-adapty/operations/setTransaction) để chúng xuất hiện.
- **Sự kiện không có hồ sơ người dùng**: Các giao dịch đến trước khi SDK xác định được người dùng — ví dụ như thông báo từ server của cửa hàng. Để đưa chúng vào xuất dữ liệu, hãy bật **Include events without profile** trong tích hợp [S3](s3-exports) hoặc [Google Cloud Storage](google-cloud-storage).
:::
:::note Trạng thái gửi của AppsFlyer, Facebook Ads và Branch có thể không chính xác vì chúng không phải lúc nào cũng trả về lỗi khi có sự cố xảy ra. ::: Để xem hồ sơ người dùng đã khởi tạo giao dịch, nhấp vào nút **View Profile** trong phần chi tiết sự kiện. --- # File: ab-tests --- --- title: "A/B test" description: "Tối ưu giá gói đăng ký với A/B test trong Adapty để cải thiện tỷ lệ chuyển đổi." --- :::tip Bạn có thể nhận kế hoạch A/B test khả thi mà không cần tự nghiên cứu. [Growth Autopilot](autopilot) kiểm tra paywall của bạn, so sánh với đối thủ cạnh tranh và tạo ra các gợi ý từ dữ liệu ẩn danh của hơn 20.000 ứng dụng đăng ký được Adapty theo dõi. ::: Tăng doanh thu ứng dụng bằng cách chạy A/B test trong Adapty. So sánh các flow, paywall và onboarding khác nhau để tìm ra cái nào có tỷ lệ chuyển đổi tốt nhất — không cần thay đổi code. Ví dụ, bạn có thể thử nghiệm: - Giá các gói đăng ký - Thiết kế, nội dung và bố cục paywall - Thời gian dùng thử và thời hạn gói đăng ký - Thiết kế onboarding ## Điều kiện tiên quyết \{#prerequisites\} Trước khi thiết lập A/B test, bạn cần có: - **Placements**: Một hoặc nhiều [placement](placements) nơi flow, paywall hoặc onboarding được hiển thị. - **Đối với flow**: Ít nhất hai [flow](adapty-flow-builder). - **Đối với paywall**: Ít nhất hai [paywall](paywalls). - **Đối với onboarding**: Ít nhất hai [onboarding](onboardings). :::warning Nếu bạn không sử dụng [Adapty Flow builder](adapty-flow-builder) hoặc [Adapty Paywall builder](adapty-paywall-builder), hãy [gửi lượt xem paywall tới Adapty](present-remote-config-paywalls#track-paywall-view-events) bằng `.logShowFlow()` (iOS SDK v4+) / `.logShowPaywall()`. Nếu không có phương thức này, Adapty không thể tính lượt xem paywall trong bài test và số liệu thống kê chuyển đổi sẽ không chính xác. ::: ## Các loại A/B test \{#ab-test-types\} Adapty hỗ trợ hai loại A/B test chính: - **Regular**: Chạy trên một placement flow/paywall/onboarding duy nhất. - **Crossplacement**: Chạy trên nhiều placement paywall, hiển thị cùng một biến thể cho người dùng ở khắp nơi. Hiện chỉ hỗ trợ cho paywall. Để so sánh đầy đủ về các loại, trường hợp sử dụng và quy tắc ưu tiên, xem [Các loại A/B test](ab-test-types). ## Các bước tiếp theo \{#next-steps\} - [Growth Autopilot](autopilot) — Phân tích paywall của bạn, nhận thông tin thị trường và tạo kế hoạch A/B test - [Các loại A/B test](ab-test-types) — Tìm hiểu về các loại test và khi nào nên dùng từng loại - [Tạo, chạy và dừng A/B test](run_stop_ab_tests) — Thiết lập và chạy bài test đầu tiên của bạn - [Kết quả và chỉ số A/B test](results-and-metrics) — Hiểu dữ liệu A/B test của bạn và chọn người chiến thắng --- # File: ab-test-types --- --- title: "Các loại A/B test" description: "Tìm hiểu về các loại A/B test trong Adapty." --- Adapty cung cấp hai loại A/B test, mỗi loại phù hợp với các tình huống kiểm thử khác nhau: - **A/B test thông thường:** A/B test được tạo cho một [flow](adapty-flow-builder)/[paywall](paywalls)/[onboarding](onboardings) placement duy nhất. - **A/B test đa placement:** A/B test được tạo cho nhiều paywall placement trong ứng dụng của bạn. Sau khi A/B test gán một
## Sự khác biệt chính \{#key-differences\}
| Tính năng | A/B Test thông thường | A/B Test đa placement |
| ------------------------------- |--------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
| **Đối tượng kiểm thử** | Một flow/paywall/onboarding | Tập hợp paywall thuộc một biến thể |
| **Tính nhất quán biến thể** | Biến thể được xác định riêng cho từng placement | Cùng một biến thể được dùng trên tất cả paywall placement |
| **Nhắm mục tiêu đối tượng** | Định nghĩa theo từng flow/paywall/onboarding placement | Dùng chung trên tất cả paywall placement |
| **Phân tích** | Bạn phân tích một flow/paywall/onboarding placement | Bạn phân tích toàn bộ ứng dụng trên các placement thuộc kiểm thử |
| **Phân phối trọng số biến thể** | Theo từng flow/paywall/onboarding | Theo tập hợp paywall |
| **Người dùng** | Tất cả người dùng | Chỉ người dùng mới (những người chưa thấy Adapty paywall) |
| **Phiên bản Adapty SDK** | Với flow: v4.0.0+. Bất kỳ phiên bản nào với paywall. Với onboarding: v3.8.0+ (iOS, Android, React Native, Flutter), v3.14.0+ (Unity), v3.15.0+ (KMP, Capacitor) | 3.5.0+ |
| **Phù hợp nhất cho** | Kiểm thử các thay đổi độc lập trong một flow/paywall/onboarding placement mà không xét đến kinh tế tổng thể của ứng dụng | Đánh giá chiến lược kiếm tiền tổng thể trên toàn ứng dụng |
## Logic lựa chọn A/B test \{#ab-test-selection-logic\}
**A/B test đa placement có độ ưu tiên cao hơn A/B test thông thường.** Tuy nhiên, A/B test đa placement chỉ được hiển thị cho **người dùng mới** — những người chưa từng thấy một Adapty paywall nào (phương thức SDK `getPaywall` chưa bao giờ được gọi cho họ). Điều này đảm bảo tính nhất quán của kết quả trên các placement.
Sơ đồ sau đây cho thấy logic Adapty sử dụng để chọn A/B test cho một placement:
Trên trang **A/B Tests**, các kiểm thử paywall, onboarding, flow và đa placement xuất hiện trên các tab riêng biệt.
## Giới hạn của A/B test đa placement \{#crossplacement-ab-test-limitations\}
:::warning
A/B test đa placement không thể bao gồm các placement thuộc flow hoặc onboarding.
:::
A/B test đa placement đảm bảo rằng mỗi người dùng thấy cùng một biến thể trên tất cả các placement trong kiểm thử. Điều này tạo ra các giới hạn sau:
* Chỉ người dùng mới mới có thể tham gia. Người dùng mới là người chưa thấy Adapty paywall và ứng dụng của họ chưa bao giờ gọi `getPaywall`. Adapty không thể đảm bảo chuỗi paywall nhất quán cho các người dùng khác.
* Placement đầu tiên mà người dùng gặp sẽ xác định paywall Adapty hiển thị. Bạn không thể thay đổi việc phân công người dùng hoặc đăng ký cùng một người dùng vào nhiều hơn một A/B test đa placement.
:::warning
Sau khi người dùng nhận được một paywall đa placement, họ sẽ thấy nó trong 90 ngày, ngay cả sau khi bạn dừng kiểm thử. Để thay đổi thời gian này, trong cài đặt **General**, hãy điều chỉnh **[Cross-placement variation stickiness](general#9-cross-placement-variation-stickiness)**.
:::
## Độ ưu tiên của A/B test đa placement \{#crossplacement-ab-test-priority\}
* A/B test đa placement luôn có độ ưu tiên cao hơn A/B test thông thường và A/B test onboarding. Nếu một người dùng mới đủ điều kiện cho cả A/B test đa placement và A/B test thông thường trên cùng một placement, A/B test đa placement sẽ được hiển thị.
* Khi nhiều A/B test đa placement với cùng đối tượng chia sẻ cùng một placement, Adapty tự động gán độ ưu tiên kiểm thử dựa trên thứ tự chúng được thêm vào. Kiểm thử đầu tiên có độ ưu tiên cao nhất. Bạn không thể thay đổi điều này theo cách thủ công.
* Các kiểm thử nhắm vào phân khúc người dùng nhỏ hơn tự động có độ ưu tiên cao hơn so với những kiểm thử nhắm vào phân khúc Tất cả người dùng.
:::note
Trong Analytics, một A/B test đa placement xuất hiện dưới dạng nhiều kiểm thử con, mỗi kiểm thử cho một placement. Các kiểm thử con theo mẫu đặt tên `
2. Ở góc trên bên phải, nhấn **Create A/B test**.
3. Trong cửa sổ **Create the A/B test**, nhập **Test name**. Đây là trường bắt buộc. Hãy chọn tên mô tả rõ nội dung test để bạn dễ nhận biết khi xem kết quả.
4. Điền **Test goal** để mô tả mục tiêu bạn muốn đạt được (ví dụ: tăng gói đăng ký hoặc giảm tỷ lệ rời bỏ).
5. Nhấn **Select placement** và chọn placement cho flow, paywall hoặc onboarding.
6. Thiết lập nội dung test trong bảng **Variants**. Mỗi hàng là một biến thể, mỗi cột là một placement. Thêm paywall vào từng ô giao nhau.
Theo mặc định, bảng có 2 biến thể và 1 placement. Bạn có thể thêm tối đa 20 biến thể. Khi bạn thêm placement thứ hai, test sẽ trở thành Crossplacement A/B test. Lưu ý rằng crossplacement A/B test chỉ khả dụng cho paywall.
7. Lưu test. Bạn có hai lựa chọn:
1. **Save as draft**: Test sẽ không chạy ngay. Bạn có thể khởi chạy sau từ placement hoặc danh sách A/B test. Dùng tùy chọn này để xem lại cài đặt trước khi khởi chạy.
2. **Run A/B test**: Khởi chạy test ngay lập tức. Test sẽ chạy ngay khi bạn nhấn nút này.
Sau khi lưu dưới dạng bản nháp, tiếp tục đến phần [Chạy A/B test](#run-an-ab-test).
## Chỉnh sửa A/B test \{#edit-an-ab-test\}
Bạn chỉ có thể chỉnh sửa các A/B test đang ở trạng thái bản nháp. Khi test đã chạy, bạn không thể thay đổi nó nữa. Để cập nhật test đang chạy, hãy dùng tùy chọn **Modify** — thao tác này tạo một bản sao với cùng tên để bạn có thể thực hiện thay đổi. Adapty sẽ dừng test gốc, và cả test gốc lẫn phiên bản đã chỉnh sửa sẽ hiển thị riêng biệt trong analytics của bạn.
## Chạy A/B test \{#run-an-ab-test\}
Chạy A/B test trong Adapty có nghĩa là gán test đó vào một placement để test có thể bắt đầu hiển thị paywall và onboarding cho người dùng.
1. Vào mục [A/B tests](ab-tests) từ menu chính của Adapty.
2. Đảm bảo bạn đang xem đúng danh sách — các A/B test **Paywall**, **Flow**, **Onboardings** và **Crossplacement** được hiển thị trong các tab riêng biệt mà bạn có thể chuyển đổi qua lại.
3. Chuyển sang tab **Drafts**. Chỉ các test ở dạng bản nháp mới có thể được khởi chạy.
4. Bên cạnh test bạn muốn khởi chạy, nhấn **Run A/B test**.
5. Cửa sổ **Edit A/B test** sẽ mở ra. Xem lại cài đặt và thực hiện các thay đổi cuối cùng nếu cần. Nếu thiếu placement hoặc đối tượng, hãy thêm vào ngay bây giờ.
6. Sau khi xem lại cài đặt, nhấn **Run A/B test** để bắt đầu.
Sau khi khởi chạy test, bạn có thể theo dõi tiến trình và xem dữ liệu hiệu suất trên trang [Kết quả và chỉ số A/B test](results-and-metrics).
## Dừng A/B test \{#stop-an-ab-test\}
Khi bạn dừng A/B test, test sẽ kết thúc và bạn có thể xem kết quả. Bạn cũng quyết định nội dung nào sẽ hiển thị cho người dùng trong các placement bị ảnh hưởng sau khi test kết thúc.
1. Mở mục [A/B tests](https://app.adapty.io/ab-tests) và chuyển sang tab **Live**.
2. Bên cạnh test bạn muốn dừng, nhấn vào menu ba chấm, rồi chọn **Stop A/B test**.
3. Trong cửa sổ **Stop the A/B test**, quyết định điều gì sẽ xảy ra sau khi test kết thúc. Bạn có ba lựa chọn:
| Lựa chọn | Mô tả |
|----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Display one of the tested paywalls/onboardings | Chọn paywall hoặc onboarding chiến thắng dựa trên kết quả test như doanh thu, xác suất tốt nhất (**P2BB**) và doanh thu trên 1K người dùng. Paywall hoặc onboarding này sẽ được hiển thị cho placement và đối tượng đã chọn. |
| Select paywalls/onboardings that don't participate in A/B test | Chọn bất kỳ paywall hoặc onboarding nào không thuộc A/B test hiện tại. Dùng tùy chọn này khi không có biến thể nào đáp ứng mục tiêu của bạn. |
| Don't show any specific paywall/onboarding | Đối với placement và đối tượng đã chọn, sẽ không có paywall hoặc onboarding cụ thể nào được chọn sau khi A/B test kết thúc. Thay vào đó, paywall hoặc onboarding tiếp theo dựa trên độ ưu tiên đối tượng sẽ được hiển thị. Đây là lựa chọn phù hợp nếu bạn muốn để hệ thống hiện tại quyết định paywall hoặc onboarding nào sẽ hiển thị, mà không cần chọn thủ công. |
:::note
Dừng A/B test là hành động không thể hoàn tác — test không thể được khởi chạy lại. Hãy đảm bảo bạn đã thu thập đủ dữ liệu trước khi quyết định dừng.
:::
4. Nhấn nút **Stop and complete this A/B test**.
Sau khi A/B test kết thúc, test sẽ không còn hoạt động nữa, và các paywall hoặc onboarding trong test sẽ không còn hiển thị cho người dùng mới.
Bạn vẫn có thể truy cập kết quả và chỉ số A/B test trên [trang chỉ số A/B test](results-and-metrics#metrics-controls) để xem lại hiệu suất của những người dùng đã tham gia trong thời gian test chạy. Các chỉ số có thể tiếp tục cập nhật khi các sự kiện mua hàng hoặc doanh thu mới được gán cho những người dùng đó.
---
# File: ab-test-no-paywall-variants
---
---
title: "Thêm biến thể A/B test không có flow hoặc paywall"
description: "Chạy A/B test trong đó một biến thể bỏ qua flow hoặc paywall, sử dụng cờ Remote Config để kiểm soát việc hiển thị."
---
Bạn có thể đo lường tác động của flow hoặc paywall bằng cách chạy A/B test với một biến thể trống. Một biến thể hiển thị flow/paywall; biến thể kia không hiển thị gì cả. Ứng dụng của bạn đọc một cờ từ Remote Config để quyết định có render hay không.
## Cách hoạt động \{#how-it-works\}
Cách thiết lập sử dụng hai flow/paywall trong cùng một placement:
- **Flow/Paywall A**: Flow hoặc paywall bạn muốn kiểm thử, với `show_paywall` được đặt thành `true` trong Remote Config của nó.
- **Flow/Paywall B**: Một flow hoặc paywall trống với `show_paywall` được đặt thành `false` trong Remote Config của nó.
Khi SDK trả về một flow hoặc paywall, ứng dụng của bạn đọc cờ `show_paywall`. Nếu cờ là `true`, ứng dụng render nó. Nếu cờ là `false`, ứng dụng bỏ qua việc render và người dùng tiếp tục mà không thấy gì.
## 1. Thêm cờ show_paywall vào Remote Config \{#1-add-the-show_paywall-flag-in-remote-config\}
Bạn cần hai flow hoặc paywall trong cùng một placement: Flow/Paywall A (cái bạn muốn kiểm thử) và Flow/Paywall B (một cái trống). Thêm trường `show_paywall` vào mỗi cái để ứng dụng của bạn có thể phân nhánh theo cùng một key cho cả hai biến thể.
Để thêm cờ vào Flow/Paywall A:
1. Mở mục [**Flows**](https://app.adapty.io/flows)/[**Paywalls**](https://app.adapty.io/paywalls) trong menu chính của Adapty và chọn Flow/Paywall A.
2. Mở mục **Remote config**.
3. Tạo một trường với tên `show_paywall` và giá trị `true`. Trong chế độ xem **JSON**, mục này trông như sau:
```json showLineNumbers
{
"show_paywall": true
}
```
4. Lưu các thay đổi.
Lặp lại các bước tương tự cho Flow/Paywall B, nhưng đặt `show_paywall` thành `false`.
Để biết thêm chi tiết về Remote Config, xem [Tùy chỉnh flow với Remote Config](customize-flow-with-remote-config) hoặc [Thiết kế paywall với Remote Config](customize-paywall-with-remote-config).
:::tip
Đặt `show_paywall` trên cả hai biến thể giúp đường dẫn code giống nhau cho cả hai nhóm và giúp việc mở rộng thêm biến thể sau này dễ dàng hơn.
:::
## 2. Thiết lập A/B test \{#2-set-up-the-ab-test\}
1. [Tạo một A/B test](run_stop_ab_tests) trên placement và thêm cả hai flow/paywall làm biến thể.
2. Đặt trọng số biến thể để phân chia lưu lượng giữa người dùng thấy flow/paywall và người dùng không thấy.
## 3. Kiểm tra cờ trong ứng dụng của bạn \{#3-check-the-flag-in-your-app\}
Đọc `show_paywall` từ Remote Config được SDK trả về. Nếu cờ là `false`, bỏ qua việc render và để người dùng tiếp tục.
**Revenue**: Chỉ số này hiển thị tổng số tiền thu được bằng USD từ các giao dịch mua và gia hạn, trừ đi các khoản hoàn tiền đã trả cho người dùng. Nó bao gồm cả lần mua đầu tiên và các lần gia hạn gói đăng ký tiếp theo. Revenue giúp bạn hiểu mỗi biến thể A/B test đang hoạt động thế nào về mặt tài chính và xác định biến thể nào mang lại nhiều doanh thu nhất.
Tìm hiểu thêm về các chỉ số [paywall](paywall-metrics).
**Probability to be best**: Adapty sử dụng một framework phân tích toán học mạnh mẽ để phân tích kết quả A/B test và cung cấp chỉ số gọi là Probability to be best. Chỉ số này đánh giá khả năng một biến thể cụ thể là lựa chọn hoạt động tốt nhất (về mặt doanh thu dài hạn) trong số tất cả các biến thể được kiểm tra. Chỉ số được biểu thị dưới dạng phần trăm từ 1% đến 100%. Để biết thông tin chi tiết về cách Adapty tính toán chỉ số này, vui lòng tham khảo [tài liệu.](maths-behind-it) Lựa chọn hoạt động tốt nhất, được xác định bởi Revenue per 1K user, được đánh dấu màu xanh lá cây và tự động được chọn làm lựa chọn mặc định.
**Revenue per 1K users**: Chỉ số revenue per 1K users tính toán doanh thu trung bình được tạo ra trên mỗi 1.000 người dùng cho mỗi biến thể A/B test. Chỉ số này giúp bạn hiểu hiệu quả doanh thu của các biến thể, bất kể tổng số người dùng là bao nhiêu. Nó cho phép bạn so sánh hiệu suất của các biến thể khác nhau trên một thang đo chuẩn hóa và đưa ra quyết định sáng suốt dựa trên hiệu quả tạo ra doanh thu.
**Prediction intervals for revenue 1K users**: Chỉ số revenue per 1K users cũng bao gồm các khoảng dự đoán. Các khoảng dự đoán này đại diện cho phạm vi mà doanh thu thực sự trên mỗi 1.000 người dùng cho một biến thể nhất định được dự đoán sẽ nằm trong đó, dựa trên dữ liệu hiện có và phân tích thống kê.
Trong bối cảnh A/B testing, khi phân tích doanh thu được tạo ra bởi các biến thể khác nhau, chúng tôi tính toán doanh thu trung bình trên mỗi 1.000 người dùng cho mỗi biến thể. Vì doanh thu có thể khác nhau giữa các người dùng, các khoảng dự đoán cung cấp dấu hiệu rõ ràng về các giá trị hợp lý cho doanh thu trên mỗi 1.000 người dùng, có tính đến sự biến đổi và không chắc chắn liên quan đến quá trình dự đoán.
Bằng cách tích hợp các khoảng dự đoán vào chỉ số revenue per 1K users, Adapty cho phép bạn đánh giá hiệu quả doanh thu của các biến thể A/B test trong khi xem xét phạm vi kết quả doanh thu tiềm năng. Thông tin này giúp bạn đưa ra quyết định dựa trên dữ liệu và tối ưu hóa chiến lược gói đăng ký một cách hiệu quả, bằng cách tính đến sự không chắc chắn trong quá trình dự đoán và các giá trị hợp lý cho doanh thu trên mỗi 1.000 người dùng.
Bằng cách phân tích các chỉ số này do Adapty cung cấp, bạn có thể có được cái nhìn sâu sắc về hiệu suất tài chính, ý nghĩa thống kê và hiệu quả doanh thu của các biến thể A/B test, cho phép bạn đưa ra quyết định dựa trên dữ liệu và tối ưu hóa chiến lược gói đăng ký một cách hiệu quả.
## Chỉ số A/B test \{#ab-test-metrics\}
Adapty cung cấp một bộ chỉ số toàn diện để giúp bạn đo lường hiệu quả hiệu suất của A/B test được thực hiện trên các biến thể paywall hoặc onboarding của bạn. Các chỉ số này được cập nhật liên tục theo thời gian thực, ngoại trừ lượt xem được cập nhật định kỳ. Hiểu các chỉ số này sẽ giúp bạn đánh giá hiệu quả của các biến thể khác nhau và đưa ra quyết định dựa trên dữ liệu để tối ưu hóa chiến lược paywall hoặc onboarding của bạn.
Các chỉ số A/B test có sẵn trong danh sách A/B test, nơi bạn có thể có cái nhìn tổng quan về hiệu suất của tất cả A/B test. Chế độ xem toàn diện này cung cấp các chỉ số tổng hợp cho mỗi biến thể kiểm tra, cho phép bạn so sánh hiệu suất của chúng và xác định các khác biệt đáng kể. Để phân tích chi tiết hơn từng A/B test, bạn có thể truy cập các chỉ số chi tiết A/B Test. Phần này cung cấp các chỉ số chuyên sâu dành riêng cho A/B test đã chọn, cho phép bạn đi sâu vào hiệu suất của từng biến thể.
Tất cả các chỉ số, ngoại trừ lượt xem, được gán cho sản phẩm trong paywall hoặc onboarding.
## Lọc chỉ số theo ngày cài đặt \{#filter-metrics-by-install-date\}
---
no_index: true
---
Các chỉ số về paywall, trial và mua hàng có thể được nhóm theo hai loại ngày khác nhau:
- **Ngày sự kiện** — khi paywall được xem, trial bắt đầu, hoặc giao dịch mua xảy ra.
- **Ngày cài đặt** — khi người dùng lần đầu mở ứng dụng.
Hai chế độ xem này có thể hiển thị các con số rất khác nhau cho cùng một khoảng thời gian. Hộp kiểm **Filter metrics by install date** kiểm soát chế độ nào được dashboard sử dụng:
- **Bỏ chọn (mặc định)**: Các chỉ số được nhóm theo ngày sự kiện.
- **Đã chọn**: Các chỉ số được nhóm theo ngày cài đặt.
**Ví dụ.** Bạn đặt khoảng thời gian từ ngày 1–30 tháng 4 và xem các trial.
- **Bỏ chọn**: Hiển thị các trial *bắt đầu* trong tháng 4, bất kể người dùng đó cài đặt ứng dụng khi nào.
- **Đã chọn**: Hiển thị các trial từ những người dùng *đã cài đặt* trong tháng 4, bất kể trial của họ bắt đầu khi nào.
Dùng chế độ xem theo ngày cài đặt để đo hiệu quả thu hút người dùng cho một cohort cụ thể. Dùng chế độ xem theo ngày sự kiện để đo hoạt động paywall hoặc onboarding trong một khoảng thời gian cụ thể.
## Điều khiển chỉ số \{#metrics-controls\}
Hệ thống hiển thị các chỉ số dựa trên khoảng thời gian đã chọn và sắp xếp chúng theo tham số cột bên trái với ba mức thụt lề.
### Khoảng thời gian \{#time-ranges\}
Bạn có thể chọn từ nhiều khoảng thời gian để phân tích dữ liệu chỉ số, cho phép bạn tập trung vào các khoảng thời gian cụ thể như ngày, tuần, tháng hoặc phạm vi ngày tùy chỉnh.
### Bộ lọc và nhóm có sẵn \{#available-filters-and-grouping\}
:::link
Bài viết chính: [Điều khiển analytics](controls-filters-grouping-compare-proceeds)
:::
Adapty cung cấp các công cụ mạnh mẽ để lọc và tùy chỉnh phân tích chỉ số phù hợp với nhu cầu của bạn. Với trang chỉ số của Adapty, bạn có thể truy cập vào nhiều khoảng thời gian, tùy chọn nhóm và khả năng lọc khác nhau.
- ✅ Lọc theo: Đối tượng, attribution, quốc gia, paywall, trạng thái paywall, nhóm paywall, onboarding, placement, quốc gia, cửa hàng, sản phẩm và cửa hàng sản phẩm.
- ✅ Nhóm theo: Sản phẩm và cửa hàng.
:::note
Khi bạn lọc theo A/B test, các A/B test xuyên placement xuất hiện dưới dạng các test con riêng lẻ (ví dụ: `My test child-0`, `My test child-1`), mỗi placement một test. Xem [Giới hạn của A/B test xuyên placement](ab-test-types#crossplacement-ab-test-limitations) để biết chi tiết.
:::
## Biểu đồ chỉ số đơn lẻ \{#single-metrics-chart\}
Một trong những thành phần chính của trang chỉ số paywall hoặc onboarding là phần biểu đồ, trực quan hóa các chỉ số đã chọn và tạo điều kiện phân tích dễ dàng.
Phần biểu đồ trên trang chỉ số A/B test bao gồm biểu đồ thanh ngang trực quan hóa các giá trị chỉ số đã chọn. Mỗi thanh trong biểu đồ tương ứng với một giá trị chỉ số và có kích thước tỷ lệ, giúp dễ dàng hiểu dữ liệu ngay lập tức. Đường ngang chỉ ra khoảng thời gian đang được phân tích, và cột dọc hiển thị các giá trị số của các chỉ số. Tổng giá trị của tất cả các giá trị chỉ số được hiển thị bên cạnh biểu đồ.
Ngoài ra, nhấp vào biểu tượng mũi tên ở góc trên bên phải của phần biểu đồ sẽ mở rộng chế độ xem, hiển thị các chỉ số đã chọn trên toàn bộ đường của biểu đồ.
## Tóm tắt A/B test \{#ab-test-summary\}
Bên cạnh biểu đồ chỉ số đơn lẻ, phần tóm tắt chi tiết A/B test được hiển thị, bao gồm thông tin về trạng thái, thời gian, placement và các chi tiết liên quan khác về A/B test.
## Định nghĩa các chỉ số \{#metrics-definitions\}
Dưới đây là các chỉ số quan trọng có sẵn cho A/B test:
### Revenue \{#revenue\}
Revenue đại diện cho tổng số tiền thu được bằng USD từ các giao dịch mua và gia hạn từ A/B test. Nó bao gồm lần mua đầu tiên và các lần gia hạn gói đăng ký tiếp theo. Chỉ số revenue được tính trước khi khấu trừ hoa hồng App Store hoặc Play Store.
Tìm hiểu thêm về các chỉ số [revenue của paywall](paywall-metrics#revenue).
### CR to purchases \{#cr-to-purchases\}
Tỷ lệ chuyển đổi sang giao dịch mua đo lường hiệu quả của A/B test trong việc chuyển đổi lượt xem thành các giao dịch mua thực tế. Nó được tính bằng cách chia số lượng giao dịch mua cho số lượt xem. Ví dụ: nếu bạn có 10 giao dịch mua và 100 lượt xem, tỷ lệ chuyển đổi sang giao dịch mua sẽ là 10%.
### CR trials \{#cr-trials\}
Tỷ lệ chuyển đổi (CR) sang dùng thử là số lượt dùng thử được bắt đầu từ A/B test chia cho số lượt xem. Tỷ lệ chuyển đổi sang dùng thử đo lường hiệu quả của A/B test trong việc chuyển đổi lượt xem thành các lượt kích hoạt dùng thử. Nó được tính bằng cách chia số lượt dùng thử được bắt đầu cho số lượt xem.
### Purchases \{#purchases\}
Chỉ số purchases đại diện cho tổng số giao dịch được thực hiện trong paywall hoặc onboarding từ A/B test. Nó bao gồm các loại giao dịch mua sau:
- Các giao dịch mua mới được thực hiện.
- Chuyển đổi dùng thử của các lượt dùng thử đã được kích hoạt.
- Hạ cấp, nâng cấp và chuyển đổi ngang giữa các gói đăng ký.
- Khôi phục gói đăng ký (ví dụ: khi gói đăng ký hết hạn mà không có tự động gia hạn và sau đó được khôi phục).
Xin lưu ý rằng các lần gia hạn không được tính vào chỉ số purchases.
### Trials \{#trials\}
Chỉ số trials cho biết tổng số lượt dùng thử được kích hoạt từ A/B test.
### Trials cancelled \{#trials-cancelled\}
Chỉ số trials cancelled đại diện cho số lượt dùng thử mà tính năng tự động gia hạn đã bị tắt. Điều này xảy ra khi người dùng hủy đăng ký thủ công khỏi bản dùng thử.
### Refunds \{#refunds\}
Refunds cho A/B test đại diện cho số lượng giao dịch mua và gói đăng ký được hoàn tiền liên quan đến các biến thể đã được kiểm tra.
### Views \{#views\}
Views là số lượt xem các paywall hoặc onboarding mà A/B test bao gồm. Nếu người dùng truy cập hai lần, điều này sẽ được tính là hai lượt truy cập.
### Unique views \{#unique-views\}
Unique views là số lượt xem duy nhất của paywall hoặc onboarding. Nếu người dùng truy cập hai lần, điều này sẽ được tính là một lượt xem duy nhất.
### Probability to be the best \{#probability-to-be-the-best\}
Chỉ số Probability to be the best định lượng khả năng một biến thể cụ thể trong A/B test là lựa chọn hoạt động tốt nhất trong số tất cả các paywall hoặc onboarding đã được kiểm tra. Nó cung cấp xác suất số cho biết hiệu suất tương đối của mỗi paywall hoặc onboarding. Chỉ số được biểu thị dưới dạng phần trăm từ 1% đến 100%.
### ARPU (Average revenue per user) \{#arpu-average-revenue-per-user\}
Chỉ dành cho A/B test onboarding. Đo lường doanh thu trung bình được tạo ra từ mỗi người dùng trong một khoảng thời gian cụ thể. Nó được tính bằng cách chia tổng doanh thu cho số lượng người dùng duy nhất.
### ARPPU (Average revenue per paying user) \{#arppu-average-revenue-per-paying-user\}
ARPPU là viết tắt của Average Revenue Per Paying User từ A/B test. Nó được tính bằng tổng doanh thu chia cho số lượng người dùng trả tiền duy nhất. Ví dụ: nếu bạn đã tạo ra $15.000 doanh thu từ 1.000 người dùng trả tiền, ARPPU sẽ là $15.
### ARPAS (Average revenue per active subscriber) \{#arpas-average-revenue-per-active-subscriber\}
ARPAS là chỉ số cho phép bạn đo lường doanh thu trung bình được tạo ra trên mỗi người đăng ký hoạt động từ việc chạy A/B test. Nó được tính bằng cách chia tổng doanh thu cho số lượng người đăng ký đã kích hoạt dùng thử hoặc gói đăng ký. Ví dụ: nếu tổng doanh thu là $5.000 và bạn có 1.000 người đăng ký, ARPAS sẽ là $5. Chỉ số này giúp đánh giá tiềm năng kiếm tiền trung bình trên mỗi người đăng ký.
### Proceeds \{#proceeds\}
Chỉ số proceeds cho A/B test đại diện cho số tiền thực tế nhận được bởi chủ ứng dụng bằng USD từ các giao dịch mua và gia hạn sau khi khấu trừ hoa hồng App Store / Play Store áp dụng. Nó phản ánh doanh thu ròng liên quan cụ thể đến các biến thể được kiểm tra trong A/B test, đóng góp trực tiếp vào thu nhập của ứng dụng. Để biết thêm thông tin về cách tính proceeds, bạn có thể tham khảo [tài liệu](analytics-cohorts#revenue-vs-proceeds) Adapty.
### Unique subscribers \{#unique-subscribers\}
Chỉ số unique subscribers đại diện cho số lượng cá nhân riêng lẻ đã đăng ký hoặc kích hoạt dùng thử thông qua các biến thể trong A/B test. Nó chỉ tính mỗi người đăng ký một lần, bất kể số lượng gói đăng ký hoặc lượt dùng thử họ bắt đầu.
### Unique paid subscribers \{#unique-paid-subscribers\}
Chỉ số unique paid subscribers đại diện cho số lượng cá nhân duy nhất đã hoàn thành thành công một giao dịch mua và trở thành người đăng ký trả tiền thông qua các biến thể trong A/B test.
### Refund rate \{#refund-rate\}
Tỷ lệ hoàn tiền cho A/B test được tính bằng cách chia số lượng hoàn tiền liên quan đến các biến thể trong bài kiểm tra cho số lượng giao dịch mua lần đầu (không tính gia hạn). Ví dụ: nếu có 5 lần hoàn tiền và 1000 giao dịch mua lần đầu, tỷ lệ hoàn tiền sẽ là 0,5%.
### Unique CR purchases \{#unique-cr-purchases\}
Tỷ lệ chuyển đổi duy nhất sang giao dịch mua cho A/B test được tính bằng cách chia số lượng giao dịch mua liên quan đến các biến thể trong bài kiểm tra cho số lượt xem duy nhất. Ví dụ: nếu có 10 giao dịch mua và 100 lượt xem duy nhất, tỷ lệ chuyển đổi duy nhất sang giao dịch mua sẽ là 10%.
### Unique CR trials \{#unique-cr-trials\}
Tỷ lệ chuyển đổi duy nhất sang dùng thử cho A/B test được tính bằng cách chia số lượt dùng thử được bắt đầu liên quan đến các biến thể trong bài kiểm tra cho số lượt xem duy nhất. Ví dụ: nếu có 30 lượt dùng thử được bắt đầu và 100 lượt xem duy nhất, tỷ lệ chuyển đổi duy nhất sang dùng thử sẽ là 30%.
### Completions & unique completions \{#completions--unique-completions\}
Chỉ dành cho A/B test onboarding. Completions đếm số lần người dùng hoàn thành onboarding của bạn thông qua các biến thể trong A/B test, tức là họ đi từ màn hình đầu tiên đến màn hình cuối cùng. Nếu ai đó hoàn thành hai lần, đó là hai **completions** nhưng chỉ một **unique completion**.
### Unique completions rate \{#unique-completions-rate\}
Chỉ dành cho A/B test onboarding. Số lượng unique completion chia cho số lượt xem duy nhất. Chỉ số này giúp bạn hiểu cách mọi người tương tác với onboarding thông qua các biến thể trong A/B test và thực hiện thay đổi nếu bạn nhận thấy rằng mọi người bỏ qua nó.
---
# File: maths-behind-it
---
---
title: "Toán học đằng sau A/B test"
description: "Hiểu toán học đằng sau phân tích gói đăng ký để có cái nhìn sâu hơn về doanh thu."
---
A/B test là một kỹ thuật mạnh mẽ dùng để so sánh hiệu quả của hai phiên bản khác nhau của một flow, paywall hoặc onboarding. Mục tiêu cuối cùng là xác định phiên bản nào hiệu quả hơn dựa trên doanh thu trung bình trên mỗi người dùng trong 12 tháng. Tuy nhiên, việc chờ đủ một năm để thu thập dữ liệu và đưa ra quyết định là không thực tế. Vì vậy, doanh thu trên mỗi người dùng trong 2 tuần được dùng làm chỉ số đại diện, được chọn dựa trên phân tích dữ liệu lịch sử để xấp xỉ chỉ số mục tiêu. Để đạt được kết quả chính xác và đáng tin cậy, cần áp dụng một phương pháp thống kê mạnh mẽ có khả năng xử lý nhiều loại dữ liệu khác nhau. Thống kê Bayesian, một phương pháp phổ biến trong phân tích dữ liệu hiện đại, cung cấp một khung linh hoạt và trực quan cho A/B test. Bằng cách kết hợp kiến thức tiên nghiệm và cập nhật nó với dữ liệu mới, phương pháp Bayesian cho phép đưa ra quyết định tốt hơn trong điều kiện không chắc chắn. Tài liệu này cung cấp hướng dẫn toàn diện về phân tích toán học mà Adapty sử dụng để đánh giá kết quả A/B test và cung cấp thông tin có giá trị cho việc ra quyết định dựa trên dữ liệu.
## Phương pháp phân tích thống kê của Adapty \{#adaptys-approach-to-statistical-analysis\}
Adapty áp dụng một phương pháp phân tích thống kê toàn diện để đánh giá hiệu suất của các A/B test và cung cấp thông tin chính xác, đáng tin cậy. Phương pháp của chúng tôi bao gồm các bước chính sau:
1. **Định nghĩa chỉ số:** Để thực hiện A/B test thành công, bạn cần xác định và định nghĩa chỉ số chính phù hợp với mục tiêu cụ thể của phân tích. Adapty đã tận dụng lượng lớn dữ liệu lịch sử từ các ứng dụng gói đăng ký để xác định chỉ số nào phù hợp làm chỉ số đại diện cho mục tiêu dài hạn là doanh thu trung bình sau 1 năm — và đó là ARPU sau 14 ngày.
2. **Xây dựng giả thuyết:** Chúng tôi tạo ra hai giả thuyết cho A/B test. Giả thuyết null (H0) giả định rằng không có sự khác biệt đáng kể giữa nhóm kiểm soát (A) và nhóm thử nghiệm (B). Giả thuyết thay thế (H1) cho rằng có sự khác biệt đáng kể giữa hai nhóm trở lên.
3. **Chọn phân phối:** Chúng tôi chọn họ phân phối tốt nhất dựa trên đặc điểm dữ liệu và chỉ số chúng tôi quan sát. Lựa chọn phổ biến nhất ở đây là phân phối log-normal (có tính đến các giá trị bằng không).
4. **Tính xác suất tốt nhất:** Sử dụng phương pháp Bayesian cho A/B test, chúng tôi tính xác suất là lựa chọn tốt nhất cho mỗi biến thể paywall hoặc onboarding tham gia vào bài test. Giá trị này có liên quan đến p-value mà chúng tôi đã dùng trước đây, nhưng về bản chất đây là một phương pháp khác, mạnh mẽ hơn và dễ hiểu hơn.
5. **Diễn giải kết quả:** Xác suất tốt nhất là đúng như tên gọi của nó. Xác suất càng lớn thì khả năng một lựa chọn cụ thể là tốt nhất càng cao. Bạn cần tự xác định ngưỡng để ra quyết định, điều này nên phụ thuộc vào nhiều yếu tố khác trong tình huống cụ thể của bạn, nhưng ngưỡng xác suất thường dùng là 95%.
6. **Khoảng dự đoán:** Adapty tính toán các khoảng dự đoán cho các chỉ số hiệu suất của mỗi nhóm, cung cấp một dải giá trị mà trong đó tham số tổng thể thực sự có khả năng nằm vào. Điều này giúp định lượng sự không chắc chắn liên quan đến các chỉ số hiệu suất được ước tính.
## Xác định cỡ mẫu \{#sample-size-determination\}
Việc xác định cỡ mẫu phù hợp là rất quan trọng để có kết quả A/B test đáng tin cậy và có tính kết luận. Adapty xem xét các yếu tố như độ mạnh thống kê và kích thước hiệu ứng kỳ vọng — vẫn quan trọng ngay cả với phương pháp Bayesian — để đảm bảo cỡ mẫu đủ lớn. Các phương pháp ước tính cỡ mẫu cần thiết, riêng cho phương pháp Bayesian mà chúng tôi đang dùng, đảm bảo độ tin cậy của phân tích.
Để tìm hiểu thêm về chức năng của A/B test, chúng tôi khuyến nghị tham khảo tài liệu của chúng tôi về [tạo](ab-tests) và [chạy A/B test](run_stop_ab_tests), cũng như tìm hiểu các [chỉ số và kết quả A/B test](results-and-metrics).
Khung phân tích của Adapty cho A/B test hiện sử dụng phương pháp Bayesian, nhưng trọng tâm vẫn là định nghĩa chỉ số, xây dựng giả thuyết và lựa chọn phân phối. Tuy nhiên, thay vì xác định p-value, chúng tôi nay tính toán các phân phối hậu nghiệm và xác suất của mỗi biến thể là tốt nhất. Chúng tôi cũng xác định các khoảng dự đoán. Phương pháp được sửa đổi này, tuy vẫn toàn diện và thậm chí còn mạnh mẽ hơn, được thiết kế để cung cấp thông tin trực quan và dễ diễn giải hơn. Mục tiêu vẫn là giúp các doanh nghiệp tối ưu hóa chiến lược, cải thiện hiệu suất và thúc đẩy tăng trưởng dựa trên phân tích thống kê mạnh mẽ từ các A/B test của họ.
---
# File: autopilot-how-it-works
---
---
title: "Growth Autopilot: Cách hoạt động"
description: "Hiểu rõ logic đằng sau Growth Autopilot và tin tưởng chúng tôi giúp tăng trưởng doanh thu của bạn."
---
[Growth Autopilot](autopilot) giúp bạn xác định những thử nghiệm nên chạy dựa trên dữ liệu hiệu suất thực tế của bạn và cách các ứng dụng tương tự trên thị trường đang hoạt động. Thay vì phải đoán mò điều gì có thể hiệu quả, bạn nhận được các đề xuất cụ thể cho những bài test có khả năng cải thiện kết quả cao hơn.
Bài viết này cung cấp cái nhìn minh bạch về cách Autopilot vận hành — dữ liệu nó sử dụng, cách đánh giá các cơ hội, và lý do một số đề xuất nhất định xuất hiện. Mục tiêu là giúp bạn tự tin sử dụng nó như một phần trong quy trình tăng trưởng của mình.
## Autopilot thực sự làm gì \{#what-autopilot-actually-does\}
Autopilot phân tích các chỉ số ứng dụng và paywall của bạn để tìm ra những thử nghiệm có khả năng tăng doanh thu nhất. Nó xem xét:
- **Thiết lập hiện tại của bạn**: giá cả, dùng thử, sản phẩm, và mức độ chuyển đổi
- **Xu hướng thị trường**: cách các ứng dụng tương tự cấu trúc ưu đãi và mức giá họ đặt ra
- **Lịch sử thử nghiệm của bạn**: những thử nghiệm bạn đã chạy và những gì chúng tiết lộ
- **Tiềm năng tăng trưởng**: những thay đổi nào có khả năng tạo ra sự khác biệt nhất
Autopilot sử dụng AI để đánh giá tổng hợp các yếu tố này và chuyển chúng thành các A/B test bạn có thể khởi chạy ngay. Bạn nhận được một kế hoạch sẵn sàng mà không cần nghiên cứu đối thủ cạnh tranh hay đoán xem nên test gì tiếp theo.
## Dữ liệu đằng sau Autopilot \{#the-data-behind-autopilot\}
Mỗi đề xuất được xây dựng từ ba nguồn dữ liệu chính phối hợp với nhau.
#### Dữ liệu của chính ứng dụng bạn \{#your-apps-own-data\}
Autopilot xem xét hiệu suất hiện tại của ứng dụng bạn:
- Chỉ số chuyển đổi trên các paywall của bạn
- Cấu trúc giá và sản phẩm
Điều này cung cấp cho Autopilot một nền tảng để làm việc trước khi đề xuất bất kỳ thay đổi nào.
:::note
Chúng tôi không sử dụng dữ liệu hiệu suất của ứng dụng bạn để huấn luyện đề xuất cho các ứng dụng khác. Dữ liệu của bạn được giữ bí mật.
:::
#### Phân tích paywall \{#paywall-analysis\}
Autopilot phân tích ảnh chụp màn hình paywall của bạn và so sánh thiết kế với các mẫu đã được thiết lập bởi các ứng dụng có hiệu suất cao nhất trong danh mục của bạn. Nó đánh giá lựa chọn bố cục, nội dung, cách trình bày gói đăng ký, và các yếu tố hướng chuyển đổi như huy hiệu tiết kiệm hoặc phần đánh giá.
Phân tích này tạo ra hai loại đề xuất:
- **Đề xuất dựa trên chuẩn** dựa trên những gì các ứng dụng có hiệu suất cao làm khác biệt — mỗi đề xuất được hỗ trợ bởi một số liệu cụ thể (ví dụ: "Được sử dụng bởi 72% ứng dụng Giáo dục có hiệu suất cao").
- **Đề xuất phân tích trực quan** được AI tạo ra từ ảnh chụp màn hình của bạn, bao gồm cải thiện nội dung, thay đổi bố cục, và các điều chỉnh thiết kế khác.
Những đề xuất này được đưa trực tiếp vào [kế hoạch tăng trưởng](autopilot-growth-plan#view-the-growth-plan) của bạn dưới dạng các giả thuyết mà bạn có thể [khởi chạy thành A/B test](autopilot-execute-plan).
#### Dữ liệu đối thủ cạnh tranh \{#competitor-data\}
Autopilot so sánh thiết lập của bạn với các ứng dụng tương tự trên thị trường bằng cách sử dụng thông tin công khai như giá cả, cấu trúc gói đăng ký, và các mẫu phổ biến trong danh mục của bạn. Những so sánh này theo từng quốc gia, vì giá cả và cấu trúc của đối thủ cạnh tranh khác nhau giữa các thị trường. Giá của đối thủ cạnh tranh đến từ các nguồn bên thứ ba và công khai như App Store — khác với dữ liệu mạng Adapty ẩn danh được sử dụng bởi phân tích chỉ số.
Theo cách này, bạn đang test những chiến lược đã hiệu quả với các ứng dụng giống bạn, không chỉ là các ý tưởng ngẫu nhiên. Khi xem phân tích, bạn có thể so sánh chuẩn và giá đối thủ cạnh tranh song song. Nếu các ứng dụng tương tự đang làm tốt hơn với mức giá hoặc cấu trúc khác, đó là tín hiệu tốt cho thấy cách tiếp cận tương tự cũng có thể hiệu quả với bạn.
:::tip
Autopilot tự động chọn đối thủ cạnh tranh phù hợp dựa trên những gì bạn có thể cạnh tranh thực tế. Chúng tôi thường khuyến nghị giữ nguyên những đề xuất này thay vì thêm các ứng dụng quá vượt trội hoặc quá tụt hậu. Nếu ứng dụng của bạn thuộc nhiều danh mục, bạn có thể muốn điều chỉnh danh sách để tập trung vào phân khúc thị trường phù hợp nhất.
:::
#### Chuẩn ngành \{#industry-benchmarks\}
Autopilot dựa trên dữ liệu ẩn danh từ 20.000 ứng dụng gói đăng ký được Adapty theo dõi để cho thấy bạn so sánh như thế nào với mức trung bình của danh mục trong một quốc gia cụ thể. Dữ liệu được tổng hợp trên toàn mạng và không bao giờ gắn với một ứng dụng cụ thể.
Ví dụ: phễu chuyển đổi và doanh thu trên mỗi lượt cài đặt của bạn được so sánh với mức trung bình của các ứng dụng trong danh mục và quốc gia của bạn. Điều này giúp bạn thấy liệu bạn đang hoạt động kém hơn, khoảng trung bình, hay đã vượt trước.
#### Dữ liệu thị trường địa lý \{#geographic-market-data\}
Autopilot phân tích từng thị trường địa lý riêng lẻ — dựa trên các mẫu trên mạng 20.000 ứng dụng Adapty — để xác định nơi điều chỉnh giá theo khu vực có thể mở khóa thêm doanh thu. Với mỗi quốc gia, nó đánh giá:
- **Tỷ lệ chuyển đổi**: Mức độ tỷ lệ từ cài đặt đến trả tiền so sánh với mức trung bình toàn cầu. Tỷ lệ cao hơn có thể cho thấy có chỗ để tăng giá; tỷ lệ thấp hơn có thể báo hiệu độ nhạy cảm với giá.
- **Chỉ số giá**: Vị trí của quốc gia trong [Adapty Pricing Index](https://uploads.adapty.io/adapty_pricing_index.pdf), cho biết sức mua của người dân quốc gia đó.
Bạn có thể thực hiện theo các đề xuất này bằng cách tạo A/B test từ [đề xuất giá theo địa lý](autopilot-growth-plan#geo-pricing-hypotheses) trong kế hoạch tăng trưởng của bạn.
## Cách Autopilot quyết định đề xuất gì \{#how-autopilot-decides-what-to-recommend\}
Autopilot tạo ra một tập hợp các đề xuất để cải thiện tỷ lệ chuyển đổi paywall của bạn. Những đề xuất này được thiết kế để thử nghiệm từng cái một nhằm đo lường đáng tin cậy tác động của từng thay đổi.
Đây là cách Autopilot đưa ra đề xuất:
1. **Tìm cơ hội lớn nhất**
Autopilot xem xét giá, sản phẩm và hiệu suất phễu của bạn, sau đó so sánh với các mẫu ngành và các ứng dụng tương tự. Phân tích chạy theo đơn vị tiền tệ của thị trường chính của bạn — không chỉ USD — vì vậy các đề xuất về giá phù hợp với mức giá người đăng ký của bạn thực sự trả. Nó tìm kiếm nơi bạn có nhiều cơ hội cải thiện nhất, dù đó là điều chỉnh giá, thêm dùng thử, hay thay đổi cấu trúc ưu đãi.
2. **Chọn thử nghiệm tiếp theo**
Mỗi giả thuyết được tạo ra dựa trên lịch sử test hiện có của bạn. Autopilot biết những thử nghiệm bạn đã chạy, thử nghiệm nào thắng, và những hướng nào vẫn còn đáng khám phá. Đề xuất tiếp theo xây dựng dựa trên những gì thử nghiệm trước tiết lộ thay vì theo một trình tự cố định.
3. **Chạy test người thắng so với người thách thức**
Sau mỗi thử nghiệm, người thắng trở thành nền tảng mới của bạn. Kết quả đó định hình đề xuất tiếp theo trong kế hoạch tăng trưởng của bạn — Autopilot giữ lại những gì đã hiệu quả, loại bỏ những gì không hiệu quả, và chọn test tiếp theo từ đó.
4. **Giữ nó thực tế**
Autopilot chỉ đề xuất các test bạn có thể khởi chạy với sản phẩm và thiết lập hiện có, hoặc với những thay đổi nhỏ như tạo sản phẩm mới hoặc điều chỉnh giá. Mục tiêu là giữ cho việc test nhanh và dễ quản lý.
5. **Hiển thị lý do**
Với mỗi đề xuất, Autopilot cung cấp một giả thuyết rõ ràng giải thích chính xác lý do test này đáng chạy. Bạn sẽ thấy các chỉ số hiện tại của bạn so sánh với đối thủ cạnh tranh và mức trung bình ngành như thế nào, cơ hội là gì, và những chỉ số chính nào chúng tôi kỳ vọng sẽ cải thiện.
Điều này biến việc thử nghiệm thành một quy trình có thể lặp lại nơi mỗi test dạy bạn điều gì đó và đưa bạn đến gần hơn với một paywall hiệu quả hơn.
## Điều gì xảy ra sau mỗi thử nghiệm \{#what-happens-after-each-experiment\}
Các đề xuất không bao giờ cạn kiệt. Mỗi test đã hoàn thành trở thành cơ sở cho các thử nghiệm tiếp theo. Miễn là bạn tiếp tục test, Autopilot tiếp tục đề xuất những gì nên thử tiếp theo.
Để làm mới dữ liệu thị trường bên dưới, hãy chạy lại phân tích trên cùng một placement. Mỗi lần chạy lại sẽ kéo vào giá đối thủ cạnh tranh được cập nhật, chuẩn chuyển đổi và xu hướng danh mục, đồng thời đóng góp bất kỳ giả thuyết mới được xác định nào vào kế hoạch tăng trưởng của bạn mà không làm xáo trộn những gì đã có. Các giả thuyết do AI tạo ra, giả thuyết tùy chỉnh, và A/B test đang tiến hành của bạn được bảo toàn qua các lần chạy lại.
Sau khi bạn đã tối ưu hóa nền tảng của mình, bạn cũng có thể chọn cạnh tranh với các đối thủ tiên tiến hơn. Cách tiếp cận lặp đi lặp lại này giúp bạn tiếp tục tối đa hóa doanh thu khi ứng dụng phát triển và thị trường thay đổi.
:::tip
Sẵn sàng thử? Khởi chạy [Growth Autopilot](autopilot-analysis) để phân tích paywall của bạn và tạo kế hoạch tăng trưởng với các A/B test. Sử dụng [trình hướng dẫn tích hợp](autopilot-execute-plan) để khởi chạy các test phức tạp một cách liền mạch: nó sẽ hướng dẫn bạn qua quá trình tạo sản phẩm, nhân bản paywall, và thiết lập phân khúc.
:::
---
# File: autopilot-analysis
---
---
title: "Phân tích Paywall và Thị trường"
description: "Tạo kế hoạch tăng trưởng dựa trên dữ liệu phù hợp với ứng dụng của bạn."
---
Làm theo các bước trong bài viết để chạy phân tích Growth Autopilot và tạo kế hoạch tăng trưởng.
Nếu bạn đã tạo kế hoạch tăng trưởng cho placement mục tiêu, phân tích này sẽ tạo ra các giả thuyết mới để bạn lựa chọn.
:::tip
Hãy đảm bảo bạn đáp ứng [các yêu cầu để phân tích](autopilot#prerequisites) trước khi bắt đầu.
:::
## Phân tích Paywall \{#paywall-analysis\}
### Chọn paywall để phân tích \{#select-a-paywall-for-analysis\}
1. Mở trang **Growth Autopilot** và nhấn nút [Get Growth plan](https://app.adapty.io/ab-tests/analysis/start).
2. Trên trang **Paywall Diagnostic**, chọn **Placement** và **Paywall** từ các menu thả xuống. Adapty tự động chọn placement có doanh thu cao nhất và paywall hàng đầu của nó. Để phân tích một paywall khác, hãy đổi placement trước.
3. Tải lên ảnh chụp màn hình. Growth Autopilot cần ảnh chụp màn hình để phân tích thiết kế và nội dung paywall của bạn.
4. Xem lại các sản phẩm đang hoạt động của paywall. Các thẻ sản phẩm ở bên phải hiển thị thời hạn gói đăng ký, giá và thời gian dùng thử của từng sản phẩm.
5. Nhấn **Confirm & Analyze** để tiếp tục. Adapty phân tích paywall của bạn và hiển thị báo cáo chẩn đoán.
### Báo cáo phân tích paywall \{#paywall-analysis-report\}
Sau khi bạn chọn paywall và tải lên ảnh chụp màn hình, Adapty phân tích paywall của bạn dựa trên các mẫu thiết kế đã được kiểm chứng, làm nổi bật cả những điểm tốt lẫn cơ hội cải thiện.
#### Những gì đang hoạt động tốt \{#whats-working-well\}
Phần này làm nổi bật việc bạn sử dụng các mẫu đã được kiểm chứng giúp tối đa hóa tỷ lệ chuyển đổi. Ví dụ: huy hiệu tiết kiệm dễ thấy, phần đánh giá của người dùng nổi bật, hoặc bảng phân tích gói đăng ký rõ ràng.
#### Những gì cần cải thiện trên paywall \{#what-to-fix-on-your-paywall\}
Adapty phân loại các đề xuất thành hai nhóm:
- **Đề xuất dựa trên benchmark**: Gợi ý dựa trên dữ liệu từ các ứng dụng hoạt động tốt nhất trong danh mục của bạn. Mỗi đề xuất bao gồm một số liệu benchmark (ví dụ: "Được dùng bởi 72% ứng dụng Giáo dục hoạt động tốt nhất") và mô tả những gì cần thay đổi.
- **Đề xuất từ phân tích hình ảnh**: Gợi ý do AI tạo ra dựa trên ảnh chụp màn hình paywall của bạn. Bao gồm: cải thiện nội dung, thay đổi bố cục và nhiều hơn nữa.
:::tip
[Kế hoạch tăng trưởng](autopilot-growth-plan#view-the-growth-plan) của bạn sẽ bao gồm các giả thuyết dựa trên các đề xuất benchmark. Bạn có thể thêm các gợi ý từ phân tích hình ảnh vào kế hoạch theo cách thủ công.
:::
Nhấn **Get Market Insights** để tiếp tục.
## Phân tích Thị trường và Đối thủ cạnh tranh \{#market-and-competitor-analysis\}
:::note
Phân tích thị trường và đối thủ cạnh tranh yêu cầu hoàn thành [phân tích paywall](#paywall-analysis) trước.
:::
Phân tích Market Insights so sánh giá cả và chỉ số chuyển đổi của ứng dụng bạn với các đối thủ cạnh tranh và mức trung bình ngành. Các so sánh được thực hiện theo từng quốc gia. Để cung cấp benchmark, Adapty tổng hợp và phân tích dữ liệu từ các ứng dụng trên App Store trong danh mục phụ và quốc gia của bạn — thông tin này không có sẵn ở nơi khác.
### Chọn đối thủ cạnh tranh \{#select-competitors\}
Chọn tối đa 5 đối thủ cạnh tranh để so sánh.
Adapty sẽ tự động chọn 5 đối thủ và gợi ý thêm 5 đối thủ nữa. Bạn có thể thêm ứng dụng thủ công bằng liên kết App Store. Để có kết quả tốt hơn, hãy chọn các ứng dụng có MRR cao hơn của bạn.
Nhấn **Generate report** để xác nhận danh sách và chờ phân tích hoàn tất.
### Chọn quốc gia \{#select-a-country\}
Sử dụng menu thả xuống Country để chọn một trong các quốc gia hàng đầu của bạn để phân tích chi tiết.
### Phân phối doanh thu \{#revenue-distribution\}
Biểu đồ phân phối doanh thu này cho thấy doanh thu của bạn đến từ những quốc gia nào, kèm theo tỷ lệ phần trăm chi tiết. Nó làm nổi bật 5 quốc gia hàng đầu của bạn, là trọng tâm của phần còn lại trong phân tích.
### Giá của đối thủ cạnh tranh \{#competitor-pricing\}
Bảng giá đối thủ cạnh tranh so sánh giá gói đăng ký của paywall với giá của các đối thủ tại [quốc gia đã chọn](#select-a-country). Bảng bao gồm các cột riêng cho từng thời hạn gói đăng ký.
### Phễu chuyển đổi \{#conversion-funnel\}
Biểu đồ hiển thị tỷ lệ chuyển đổi của bạn — Views-to-Trial, Trial-to-Paid và Views-to-Paid — bên cạnh mức trung bình của các ứng dụng tương tự.
### Phân phối doanh thu theo thời hạn \{#revenue-distribution-by-duration\}
Biểu đồ này cho thấy các thời hạn gói đăng ký nào đóng góp nhiều nhất vào doanh thu của bạn, so với mức trung bình ngành. Nếu doanh thu của bạn tập trung quá nhiều vào một thời hạn, điều đó có thể cho thấy cơ hội tối ưu hóa chiến lược định giá.
### Activation ARPU \{#activation-arpu\}
Biểu đồ **Activation ARPU: your app vs. category** so sánh doanh thu trung bình trên mỗi lượt cài đặt mới của ứng dụng với mức trung bình của danh mục.
Sử dụng biểu đồ này kết hợp với [phễu chuyển đổi](#conversion-funnel):
- Chuyển đổi cho thấy có bao nhiêu người dùng trả tiền.
- Activation ARPU cho thấy thu nhập trung bình trên mỗi người dùng.
Tỷ lệ chuyển đổi cao nhưng Activation ARPU thấp có thể cho thấy các ưu đãi đang được định giá thấp hơn mức cần thiết.
Chỉ số này **dựa trên cohort**. Adapty lấy những người dùng đã cài đặt ứng dụng trong 90 ngày qua và chia doanh thu họ tạo ra cho số lượng của họ.
#### So sánh với các chỉ số khác \{#comparison-to-other-metrics\}
Activation ARPU sẽ không khớp với các giá trị ARPU bạn thấy ở nơi khác trong dashboard — mỗi chỉ số đo lường một điều khác nhau.
- **[Biểu đồ phân tích ARPU](arpu)**: Bao gồm các lần gia hạn từ các cohort cũ hơn, vì vậy con số cao hơn Activation ARPU nhiều lần.
- **[Biểu đồ doanh thu](revenue), bộ lọc Period đặt thành "Activation"**: Chỉ tính lần thanh toán đầu tiên của mỗi người dùng. Không tính các lần gia hạn được thực hiện bởi cohort trong khoảng thời gian 90 ngày.
- **[Doanh thu cohort](analytics-cohorts) (90 ngày)**: Tương đương gần nhất — sử dụng chỉ số này làm tham chiếu.
## Các bước tiếp theo \{#next-steps\}
Đọc [Thực thi kế hoạch tăng trưởng của bạn](autopilot-execute-plan) để tìm hiểu cách chạy các A/B test dựa trên kết quả phân tích.
Bạn luôn có thể xem lại báo cáo phân tích từ kế hoạch tăng trưởng — chuyển sang tab **Analysis Results**.
---
# File: autopilot-growth-plan
---
---
title: "Quản lý kế hoạch tăng trưởng"
description: "Thêm các giả thuyết tùy chỉnh, lưu trữ chúng và cập nhật kế hoạch tăng trưởng."
---
Sau khi hoàn thành [phân tích](autopilot-analysis), Adapty trình bày kế hoạch tăng trưởng của bạn — danh sách các **giả thuyết cải tiến có thể thực hiện được**. Mỗi mục gợi ý một mức giá mới hoặc cải tiến thiết kế.
Mở một giả thuyết để [thử nghiệm bằng A/B test](autopilot-execute-plan).
Mỗi placement có kế hoạch tăng trưởng riêng. Khi điều kiện thị trường thay đổi, bạn có thể chạy lại phân tích để làm mới các gợi ý. Các lần chạy trước vẫn được lưu trong lịch sử phiên bản.
## Giả thuyết \{#hypotheses\}
Chuyển đổi giữa các tab ở đầu kế hoạch tăng trưởng để lọc giả thuyết theo loại:
- **Top priority** bao gồm các giả thuyết có tác động cao nhất đáng được chú ý. Khi không có giả thuyết nào đủ điều kiện, tab này sẽ bị ẩn.
- **All** hiển thị tất cả các giả thuyết trong kế hoạch đang hoạt động của bạn.
- Giả thuyết **Pricing** khám phá các mức giá mới hoặc cấu hình dùng thử. Mỗi giả thuyết dựa trên một khuyến nghị cụ thể từ báo cáo chẩn đoán paywall hoặc thông tin thị trường.
- Giả thuyết **Visual** là các gợi ý cải tiến thiết kế. Chúng có thể liên quan đến thay đổi nội dung, bố cục hoặc các yếu tố trực quan khác.
- Giả thuyết [**Geo-pricing**](#geo-pricing-hypotheses) kiểm tra các điều chỉnh giá theo từng quốc gia.
- Giả thuyết [**Archived**](#archive-a-hypothesis) là các gợi ý bạn đã xóa khỏi kế hoạch đang hoạt động. Bạn có thể khôi phục chúng bất cứ lúc nào.
Bạn có thể [thêm giả thuyết của riêng mình](#add-your-own-hypothesis) hoặc [lưu trữ](#archive-a-hypothesis) những giả thuyết bạn không muốn kiểm tra.
Kiểm tra các giả thuyết này từng cái một, theo bất kỳ thứ tự nào. Các bài kiểm tra geo-pricing là ngoại lệ — đối tượng của chúng không trùng lặp nên có thể chạy song song.
### Giả thuyết Geo-pricing \{#geo-pricing-hypotheses\}
:::important
Sản phẩm mua một lần không đủ điều kiện để tối ưu hóa giá theo khu vực.
:::
Mở tab **Geo-pricing** để xem danh sách các khuyến nghị geo-pricing. Mỗi khuyến nghị nhắm mục tiêu một quốc gia với một thay đổi giá duy nhất và chạy dưới dạng A/B test riêng biệt.
Adapty phát hiện các quốc gia cần điều chỉnh giá và cung cấp các khuyến nghị dựa trên dữ liệu được xác thực bởi [Adapty Pricing Index](https://uploads.adapty.io/adapty_pricing_index.pdf).
## Đọc biểu đồ funnel từng bước \{#funnel-chart-step-by-step\}
Hãy cùng đi qua từng thành phần của funnel để hiểu cách đọc hành trình người dùng trên biểu đồ.
### Lượt cài đặt \{#installs\}
Cột thứ 1 (1) là số lượt cài đặt. Nó được hiển thị dưới dạng giá trị tuyệt đối (2) của tổng số lần cài đặt (không phải người dùng duy nhất) và cũng là 100% — số đầu vào lớn nhất để tính toán tỷ lệ chuyển đổi tương đối cho các bước tiếp theo. Nếu người dùng xóa ứng dụng rồi cài lại, sẽ được tính là hai lượt cài đặt riêng biệt.
Vùng màu xám bên cạnh thể hiện các thông số chuyển tiếp giữa các bước. Tỷ lệ chuyển đổi sang bước tiếp theo (Paywall hiển thị) được hiển thị trên một nhãn (3). Tỷ lệ thoát và giá trị tuyệt đối của lượt rời bỏ được hiển thị bên dưới (4).
### Paywall hiển thị \{#paywall-displayed\}
Cột thứ 2 (5) hiển thị số người dùng đã xem paywall ít nhất một lần (6). Chỉ tính những người dùng có lượt cài đặt trong khoảng thời gian được chọn. Nếu người dùng xem paywall trong khoảng thời gian đã chọn nhưng ngày cài đặt nằm ngoài phạm vi thì lượt xem đó không được tính.
Cũng có tỷ lệ phần trăm của các lượt xem đó so với bước thứ 1 (7). Bạn có thể nhận thấy phần trăm này bằng với nhãn màu xám (3) của bước 1. Sự bằng nhau này chỉ xảy ra ở hai bước đầu tiên.
Chúng tôi thu thập dữ liệu cho bước này từ tất cả các paywall sử dụng phương thức `logShowFlow()` (iOS SDK v4+) / `logShowPaywall()`. Vì vậy, hãy đảm bảo gửi mọi lượt xem paywall đến Adapty bằng phương thức này như mô tả trong [tài liệu](present-remote-config-paywalls#track-paywall-view-events).
Vùng màu xám bên cạnh cột thứ 2 thể hiện quá trình chuyển tiếp. Tỷ lệ chuyển đổi sang bước tiếp theo (Dùng thử) được hiển thị trên nhãn (8). Tỷ lệ thoát và giá trị tuyệt đối của khách hàng rời bỏ sau paywall được hiển thị bên dưới (9).
### Dùng thử \{#trials\}
Cột thứ 3 (10) hiển thị số lượt dùng thử được kích hoạt trên các paywall bởi những khách hàng đã cài đặt ứng dụng trong khoảng thời gian đã chọn (11). Nếu bộ lọc được đặt cho sản phẩm không có dùng thử, giá trị này bằng 0 và cột sẽ trống.
Cũng xem tỷ lệ phần trăm của lượt dùng thử tính từ bước thứ 1, thể hiện tỷ lệ chuyển đổi từ lượt cài đặt sang dùng thử (12).
Bạn có thể nhận thấy phần trăm này không bằng với nhãn màu xám (8) của bước chuyển đổi trước. Đó là vì chúng tôi so sánh giá trị hiện tại với bước thứ 1 ở đầu biểu đồ và với bước trước đó trên các nhãn màu xám.
Vì vậy, vùng màu xám bên cạnh cột thứ 3 hiển thị tỷ lệ chuyển đổi sang bước tiếp theo (Đã trả tiền) được thể hiện trên nhãn (13). Tỷ lệ thoát và giá trị tuyệt đối của khách hàng rời bỏ trong thời gian dùng thử được hiển thị bên dưới (14).
### Gói đăng ký và gia hạn \{#subscriptions-and-renewals\}
Cột thứ 4 hiển thị số gói đăng ký đã được kích hoạt (15). Đối với các sản phẩm không có dùng thử, con số này bao gồm các gói đăng ký trực tiếp từ paywall. Đối với các sản phẩm có dùng thử, nó chứa số lượt dùng thử chuyển đổi thành gói đăng ký trả tiền. Nếu bạn có cả hai loại sản phẩm, có và không có dùng thử, đây sẽ là tổng của cả hai.
Tỷ lệ phần trăm ở trên cùng cho thấy tỷ lệ chuyển đổi từ lượt cài đặt (16).
Tỷ lệ phần trăm trên nhãn màu xám cho thấy tỷ lệ chuyển đổi sang bước tiếp theo (gia hạn sang kỳ thứ 2) (17).
Tỷ lệ phần trăm và giá trị tuyệt đối của lượt thoát trước khi gia hạn sang kỳ thứ 2 được hiển thị bên dưới tỷ lệ chuyển đổi (18).
Bước này bắt đầu một chuỗi các bước có cấu trúc tương tự. Sau lần gia hạn thứ 2 là lần thứ 3, rồi thứ 4, v.v. Nếu có đủ dữ liệu trong lịch sử ứng dụng của bạn, bạn có thể xem hàng chục kỳ bằng cách cuộn ngang. Logic cho các bước này vẫn giống nhau:
- tỷ lệ phần trăm từ lượt cài đặt ở trên cùng,
- tỷ lệ phần trăm từ bước trước ở dưới cùng,
- số lượng gia hạn tuyệt đối ở trên cùng,
- số lượng thoát tuyệt đối ở dưới cùng,
- hover để xem popup lý do thoát.
### Lý do thoát \{#churn-reasons\}
Adapty cung cấp thống kê chi tiết về *tỷ lệ thoát* từ giai đoạn Dùng thử trở đi. Mỗi người dùng bước vào một giai đoạn nhưng không tiến đến giai đoạn tiếp theo đều được tính là một trường hợp thoát.
* Nếu một sự kiện cụ thể (ví dụ: hết hạn dùng thử hoặc vấn đề thanh toán) là nguyên nhân khiến không có chuyển đổi, Adapty sẽ hiển thị lý do đó.
* Trạng thái **unknown** là trạng thái tạm thời. Nó cho biết người dùng chưa gặp phải sự kiện cho phép họ tiến sang giai đoạn tiếp theo.
Ở giai đoạn Dùng thử, điều này thường có nghĩa là thời gian dùng thử chưa kết thúc. Điều này thường xảy ra khi xem Funnel cho các khoảng thời gian ngắn hoặc trong một ngày, vì dùng thử cần thời gian để xử lý.
Adapty sẽ cập nhật thông tin khi người dùng chuyển đổi hoặc hủy dùng thử.
### Chế độ xem bảng, bộ lọc và xuất CSV \{#table-view-filters-and-csv-export\}
Biểu đồ funnel được bổ sung thêm dữ liệu dạng bảng để cung cấp tài liệu tiện lợi cho công việc với các con số.
Bảng này lặp lại cách tiếp cận của funnel với một số điều chỉnh.
Có các cột hiển thị dữ liệu cho tất cả các bước ngoại trừ bước gói đăng ký trả tiền đầu tiên.
Thay vào đó, có hai cột riêng biệt: Cài đặt -> Trả tiền và Dùng thử -> Trả tiền. Chúng hiển thị điểm cốt lõi của quá trình chuyển đổi khi người dùng miễn phí trở thành người dùng trả tiền.
Có thể trông như thể có sự phân chia theo loại sản phẩm: cột Cài đặt -> Trả tiền chỉ hiển thị các sản phẩm không có dùng thử trong khi cột Dùng thử -> Trả tiền chứa các sản phẩm có dùng thử. Nhưng đó không hoàn toàn là cách hoạt động. Vì chúng tôi cũng xem xét những người dùng đã hết hạn dùng thử và mua sản phẩm có dùng thử như thể nó không có dùng thử.
Đi sâu hơn vào các con số, bạn sẽ thấy các công cụ lọc mạnh mẽ để đưa ra các giả thuyết mới.
Thoải mái đặt điều kiện theo nhiều chiều khác nhau. Thu thập thông tin chính xác dựa trên dữ liệu.
Thử nghiệm với:
1. Loại sản phẩm — kinh tế, thời hạn, v.v.
2. Khoảng thời gian.
3. Phân khúc theo quốc gia.
4. Attribution lưu lượng truy cập.
5. Cửa hàng.
Chọn Số tuyệt đối #, Tỷ lệ % hoặc cả hai để chỉ xem dữ liệu cần thiết.
Cuối cùng, ở bên phải thanh điều khiển, có nút để xuất dữ liệu funnel sang CSV. Bạn có thể mở nó trong Excel, Google Sheets hoặc nhập vào hệ thống phân tích của riêng bạn.
:::important
Hãy thông báo cho Adapty nếu ứng dụng của bạn tham gia chương trình hoa hồng giảm. Để đảm bảo tính toán chính xác, hãy chỉ định trạng thái [Small Business Program](app-store-small-business-program) và [Reduced Service Fee program](google-reduced-service-fee) trong [cài đặt ứng dụng](general) của bạn.
:::
---
# File: analytics-retention
---
---
title: "Phân tích retention"
description: "Hiểu phân tích retention người dùng và tối ưu hóa chiến lược gói đăng ký của bạn."
---
Các biểu đồ retention có thể giúp bạn trả lời những câu hỏi sau:
1. Ứng dụng của bạn giữ chân người dùng như thế nào qua từng giai đoạn?
2. Sản phẩm nào hấp dẫn hơn và giữ chân người dùng tốt hơn?
3. Nhóm người dùng nào trung thành hơn?
4. Mức retention nào có thể dùng làm chuẩn tham chiếu để tăng trưởng?
5. Và tất nhiên, bạn có thể tiết kiệm tiền bằng cách đầu tư vào đối tượng đã thu hút thay vì tìm kiếm người dùng mới như thế nào?
Bạn sẽ tìm thấy những thông tin hữu ích về hành vi người dùng khi thiết lập các bộ lọc và nhóm.
Retention được tính toán dựa trên dữ liệu chúng tôi thu thập qua SDK và thông báo từ cửa hàng, không cần bất kỳ cấu hình bổ sung nào từ phía bạn.
### Chúng tôi tính retention như thế nào? \{#how-do-we-calculate-retention\}
Khi xem biểu đồ retention, bạn thấy số lượng người dùng thay đổi theo từng bước họ thực hiện: dùng thử (nếu checkbox "show trials" được chọn), lần thanh toán thứ 1, thứ 2, v.v. Hãy cùng làm rõ người dùng nào được tính khi bạn chọn khoảng thời gian cho biểu đồ retention.
Ví dụ: bạn chọn 3 tháng gần nhất trong lịch và checkbox "show trials" không được chọn. Điều này có nghĩa là chúng tôi chỉ tính những người có gói đăng ký đầu tiên trong 3 tháng gần nhất. Nếu checkbox "show trials" được chọn và 3 tháng gần nhất được chọn trong lịch, chúng tôi tính tất cả những người đã dùng thử trong 3 tháng gần nhất. Đối với những người đăng ký này, chúng tôi hiển thị retention tuyệt đối cho bước thứ N là số người đã thực hiện lần thanh toán thứ N. Và chúng tôi tính giá trị retention tương đối cho bước thứ N là tỷ lệ giữa số lượng tuyệt đối của lần thanh toán thứ N so với tổng số gói đăng ký (hoặc lượt dùng thử) trong khoảng thời gian đã chọn.
:::info
Retention thay đổi hồi tố
Bất kể bạn xem biểu đồ vào thời điểm nào, con số cơ sở (100%) vẫn giữ nguyên cho khoảng thời gian đã chọn. Trong khi đó, retention sang kỳ tiếp theo có thể tăng theo thời gian.
Ví dụ: với gói đăng ký hàng tháng, nếu có 20 lần mua đầu tiên trong khoảng thời gian từ ngày 1 đến ngày 31 tháng 12, thì retention sang kỳ thứ hai dự kiến sẽ tăng trong suốt tháng 1 (và có thể cả sau đó) khi người dùng bước vào kỳ đăng ký tiếp theo đúng hạn hoặc muộn hơn vì một số lý do (ví dụ: thời gian ân hạn).
:::
### Xử lý hoàn tiền \{#refund-handling\}
Các khoản hoàn tiền **không** bị loại trừ khỏi retention. Người dùng được hoàn tiền vẫn được tính trên đường cong retention, điều này có thể khiến Retention trông cao hơn so với [Active subscriptions](active-subscriptions) hoặc [Revenue](revenue) cho cùng một cohort.
Để so sánh đầy đủ giữa các chỉ số, xem [Cách các chỉ số xử lý hoàn tiền](refund-events#how-metrics-handle-refunds).
### Cơ hội từ retention \{#retention-opportunities\}
Hãy xem cách khai thác tối đa tính năng retention của Adapty.
Không chỉ đơn thuần là đam mê với con số mà còn muốn thấy giá trị kinh doanh thực sự sau khi triển khai kết quả phân tích — hãy nghĩ về mục đích trước. Khi khám phá sâu hơn các tính năng của biểu đồ, sẽ rất hay nếu làm rõ tác động mà dữ liệu này có thể mang lại.
Vì vậy, hãy cùng nhìn vào TẠI SAO và NHƯ THẾ NÀO.
1 - làm việc với đối tượng.
Trước hết, retention liên quan đến đối tượng mục tiêu, sở thích của họ, và liệu sản phẩm của bạn có đáp ứng được kỳ vọng của họ trong suốt vòng đời sử dụng hay không. Nếu bạn muốn đo lường mối quan hệ cốt lõi của doanh nghiệp tạo ra doanh thu — retention chính là công cụ dành cho bạn.
Việc đo lường này mang lại lợi ích vì thường bán hàng cho khách hiện tại rẻ hơn bán cho người lạ. Chi phí thấp hơn vì hai lý do: ít nỗ lực bán hàng hơn và giá trị đơn hàng trung bình cao hơn. Vì vậy, đầu tư vào sự trung thành của người đăng ký khi retention giảm là một ý tưởng hay.
2 - làm việc với sản phẩm.
Lý do thứ hai của TẠI SAO là biểu đồ retention cho thấy vòng đời sử dụng thực tế của sản phẩm và cho phép bạn dự báo dài hạn. Và nếu bạn muốn cải thiện, hãy điều chỉnh quy trình phân phối sản phẩm để thay đổi vòng đời của nó, rồi dự báo lại để tiến gần hơn đến mục tiêu kinh doanh. Những cập nhật như vậy có thể là một phần của tầm nhìn chiến lược kết hợp với quy trình dự báo thường xuyên. Và đúng vậy, quá trình này không bao giờ kết thúc vì chúng ta đều phải chạy nhanh chỉ để đứng yên trong môi trường không ngừng thay đổi.
3 - làm việc với thị trường.
Di chuyển nhanh hơn các đối thủ chính là tốt, nhưng đôi khi thoát ra khỏi cuộc đua thông thường lại mang lại nhiều lợi ích hơn. Khi bạn phân tích hành vi người dùng ở các quốc gia và cửa hàng khác nhau, một số đặc thù địa phương có thể mở ra những insights nổi bật và cơ hội mới cho doanh nghiệp. Bối cảnh văn hóa và thị trường có thể được phân tích từ góc độ retention để sau đó sử dụng cho phân khúc và phát triển tiếp theo. Ví dụ, bạn có thể tìm thấy vùng nước xanh ở một số khu vực và phát triển nhanh hơn ở đó.
Tất nhiên, việc sử dụng dữ liệu retention không chỉ giới hạn ở cách diễn giải cơ bản này, nhưng đây có thể là điểm khởi đầu tốt nếu bạn muốn nhanh chóng tạo ra giá trị thực.
### Đường cong, chế độ xem bảng, bộ lọc và xuất CSV \{#curves-table-view-filters-and-csv-export\}
Giờ khi chúng ta đã hiểu về mục đích retention và các cách diễn giải cơ bản, hãy xem qua các công cụ giúp mọi thứ trở nên tiện lợi.
Cốt lõi của tính năng retention trong Adapty là biểu đồ. Nó cho thấy mức retention phụ thuộc vào các bước trong vòng đời của khách hàng như thế nào.
Các bước được hiển thị trên trục ngang: Trial, Paid (gói đăng ký thứ 1), P2 (gói đăng ký thứ 2), P3, P4, v.v.
Lưu ý rằng trục bắt đầu bằng bước Trial chỉ khi checkbox "Show trials" được chọn.
Đối với tính toán dữ liệu, checkbox này hoạt động như sau. Khi "Show trials" được chọn và trục bắt đầu từ bước Trial, bạn chỉ thấy các tình huống có dùng thử — không có giao dịch trực tiếp từ lần cài đặt nào được hiển thị và bước Paid chỉ chứa các giao dịch đến từ dùng thử. Khi "Show trials" không được chọn và trục bắt đầu từ bước Paid, bước đầu tiên này chứa tất cả các giao dịch đầu tiên bao gồm cả từ dùng thử lẫn trực tiếp từ lần cài đặt.
Khi bạn di chuột qua biểu đồ, một cửa sổ bật lên hiển thị tóm tắt dữ liệu. Và nếu bạn di chuột qua một cột trong bảng bên dưới, bạn cũng thấy cửa sổ bật lên tóm tắt với dữ liệu liên quan trên biểu đồ.
Bảng chứa cùng nhóm và bộ lọc được chọn cho biểu đồ.
Hãy thoải mái kết hợp các bộ lọc và nhóm để phân tích nâng cao. Thu thập insights thực sự dựa trên dữ liệu.
Tùy chỉnh:
1. Loại sản phẩm.
2. Thời hạn.
3. Khoảng thời gian.
4. Quốc gia.
5. Attribution lưu lượng truy cập.
6. Cửa hàng.
Sử dụng tùy chọn #Absolute và %Relative để xem dữ liệu cần thiết.
Cuối cùng, ở phía bên phải của bảng điều khiển, có nút để xuất dữ liệu funnel ra CSV. Sau đó bạn có thể mở trong Excel, Google Sheets, hoặc import vào hệ thống phân tích riêng của mình để tiếp tục phân tích và dự báo trong môi trường bạn ưa thích.
:::warning
Hãy đảm bảo rằng bạn đã khai báo ứng dụng của mình tham gia Small Business Program trong [Adapty General Settings](https://app.adapty.io/settings/general).
:::
---
# File: analytics-conversion
---
---
title: "Phân tích chuyển đổi"
description: "Đo lường tỷ lệ chuyển đổi gói đăng ký bằng các công cụ phân tích của Adapty."
---
Trong khi funnel cho bạn cái nhìn tổng quan và retention tập trung vào sự trung thành của người dùng, phân tích chuyển đổi được thiết kế để giúp bạn đánh giá hiệu quả ở mọi bước quan trọng trong hành trình người dùng—theo thời gian.
Chuyển đổi giúp trả lời các câu hỏi sau:
1. Tỷ lệ chuyển đổi của ứng dụng thay đổi như thế nào theo thời gian? Có xu hướng theo mùa nào không?
2. Chuyển đổi thay đổi như thế nào tại thời điểm diễn ra các hoạt động marketing hoặc một số tình huống mới khác?
3. Người dùng ở các khu vực khác nhau phản ứng như thế nào với các bản cập nhật ứng dụng của bạn?
4. Loại sản phẩm nào có tỷ lệ chuyển đổi tốt hơn theo thời gian?
Chuyển đổi được thực hiện với dữ liệu chúng tôi thu thập qua Adapty SDK và thông báo từ cửa hàng, và không yêu cầu bất kỳ cấu hình bổ sung nào từ phía bạn.
## Điều khiển chính và biểu đồ \{#main-controls-and-charts\}
Mặc dù doanh thu thường là chỉ số chính để đo lường thành công, nhưng đó chỉ là một phần của bức tranh lớn hơn. Hiểu cách doanh nghiệp của bạn hoạt động theo thời gian—trên các hành vi người dùng và giai đoạn vòng đời khác nhau—cũng quan trọng không kém. Đó là lúc phân tích chuyển đổi phát huy tác dụng.
Bạn có thể tìm thấy thêm thông tin chi tiết về hành vi người dùng bằng cách đặt bộ lọc và nhóm. Để xác định và phân tích xu hướng, hãy theo dõi cách chuyển đổi của bạn thay đổi hàng ngày, hàng tháng hoặc hàng năm.
Ở phía bên trái của biểu đồ, bạn sẽ thấy điều khiển các bước chuyển đổi. Điều này cho phép bạn chọn các chuyển đổi cụ thể cần theo dõi—chẳng hạn như Cài đặt → Dùng thử, Dùng thử → Trả phí, hoặc Trả phí → Gia hạn.
Mỗi chỉ số chuyển đổi tuân theo logic sau:
- Đặt **X** là số người dùng đã bước vào trạng thái ban đầu vào một ngày được chọn (ví dụ: lượt cài đặt).
- Đặt **Y** là số người dùng trong nhóm đó cuối cùng đạt đến trạng thái mục tiêu (ví dụ: bắt đầu dùng thử).
- Tỷ lệ chuyển đổi được tính như sau: **Chuyển đổi = (Y / X) × 100%**
:::note
Ngày hiển thị trên biểu đồ tương ứng với thời điểm người dùng bước vào trạng thái ban đầu (X)—thời điểm họ đủ điều kiện để chuyển đổi.
:::
Vui lòng xem bên dưới để hiểu từng chuyển đổi, kèm theo ví dụ tham khảo.
### Cài đặt -> Trả phí \{#install---paid\}
Chỉ số này cho thấy tỷ lệ phần trăm người dùng đã cài đặt ứng dụng vào một ngày cụ thể và cuối cùng mua gói đăng ký đầu tiên của họ.
Cột **Predicted revenue** hiển thị tổng doanh thu ước tính mà một cohort người dùng đăng ký dự kiến sẽ tạo ra trong khoảng thời gian đã chọn sau khi cohort được tạo. Giá trị này được tính toán bằng mô hình dự đoán của Adapty, dựa trên các mô hình giữ chân cohort lịch sử của ứng dụng.
Cột **Predicted LTV** hiển thị lifetime value ước tính của mỗi người dùng trong cohort đã chọn. Giá trị này được tính bằng cách chia doanh thu dự đoán cho số lượng người dùng trả phí dự đoán trong cohort.
### Chọn khoảng thời gian dự đoán \{#select-the-horizon\}
Để thay đổi khoảng thời gian dự đoán, hãy chọn một giá trị từ menu thả xuống **Predictions**. Các tùy chọn có sẵn là 3, 6, 9, 12, 18 và 24 tháng sau khi cohort được tạo.
### Lọc theo sản phẩm \{#filter-by-product\}
Bạn có thể lọc doanh thu dự đoán và LTV theo sản phẩm. Theo mặc định, dự đoán được xây dựng từ tất cả dữ liệu mua hàng — lọc theo sản phẩm cho thấy từng sản phẩm đóng góp như thế nào.
## Khi dự đoán không khả dụng \{#when-predictions-are-unavailable\}
Khi không thể tạo dự đoán cho một cohort, các cột Predicted Revenue và Predicted LTV sẽ hiển thị dấu gạch ngang (—) thay vì giá trị. Điều này có thể xảy ra vì một số lý do:
- **Chưa đủ thời gian kể từ khi tạo cohort**: Dự đoán chỉ khả dụng sau khi cohort hoàn thành kỳ gia hạn đầu tiên — khoảng một tuần đối với gói đăng ký hàng tuần và khoảng bốn tuần đối với gói đăng ký hàng tháng.
- **Kích thước cohort nhỏ**: Quá ít người dùng trả phí để tạo ra dự báo đáng tin cậy.
- **Hành vi cohort bất thường**: Cohort có sự khác biệt đáng kể so với các mô hình mà mô hình kỳ vọng. Chờ thêm vài tuần có thể giải quyết vấn đề này khi có thêm dữ liệu tích lũy.
- **Vượt quá khoảng thời gian**: Cohort đã cũ hơn khoảng dự đoán đã chọn. Ví dụ: dự đoán 3 tháng sẽ bị ẩn sau ba tháng, dự đoán 12 tháng sau mười hai tháng, và không có dự đoán nào được hiển thị cho các cohort cũ hơn 24 tháng.
:::warning
Khi bật tính năng dự đoán, cần lưu ý rằng có thể mất tối đa 24 giờ trước khi dữ liệu dự đoán về Doanh thu và LTV xuất hiện trên Adapty dashboard của bạn.
:::
---
# File: predictions-in-ab-tests
---
---
title: "Dự đoán trong A/B test"
description: "Tìm hiểu cách dự đoán trong A/B test giúp tinh chỉnh chiến lược định giá gói đăng ký."
---
Chào mừng bạn đến với tài liệu Phân tích dự đoán của Adapty dành cho tính năng A/B test. Công cụ này sẽ cung cấp thông tin chi tiết về kết quả tương lai của các A/B test đang chạy và giúp bạn đưa ra quyết định dựa trên dữ liệu nhanh hơn 🚀 với các dự đoán được hỗ trợ bởi ML của Adapty.
### Dự đoán A/B test là gì? \{#what-are-ab-test-predictions\}
Tính năng Dự đoán A/B test của Adapty sử dụng các kỹ thuật machine learning tiên tiến (cụ thể là mô hình gradient boosting) để dự báo tiềm năng doanh thu dài hạn của các paywall được so sánh trong một A/B test.
Mô hình dự đoán này cho phép bạn chọn paywall hiệu quả nhất dựa trên doanh thu dự kiến sau một năm, thay vì chỉ dựa vào các chỉ số bạn quan sát được trong khi test đang chạy. Điều này giúp bạn xác định người chiến thắng một cách đáng tin cậy hơn và nhanh hơn, mà không cần phải chờ hàng tuần để dữ liệu tích lũy đủ.
### Mô hình hoạt động như thế nào? \{#how-does-the-model-work\}
Mô hình được huấn luyện trên dữ liệu lịch sử A/B test phong phú từ nhiều ứng dụng thuộc các danh mục khác nhau. Nó tích hợp nhiều tính năng để dự đoán doanh thu mà một paywall có khả năng tạo ra trong một năm sau khi thử nghiệm bắt đầu. Các tính năng này bao gồm:
- Giao dịch và tỷ lệ chuyển đổi của người dùng theo các khoảng thời gian khác nhau
- Phân bố địa lý của người dùng
- Nền tảng sử dụng (iOS hoặc Android)
- Tỷ lệ hủy đăng ký và hoàn tiền
- Các sản phẩm gói đăng ký và độ dài chu kỳ của chúng (hàng ngày, hàng tháng, hàng năm, v.v.)
- Dữ liệu liên quan đến giao dịch khác
Mô hình cũng tính đến các thời gian dùng thử trong paywall, sử dụng tỷ lệ chuyển đổi lịch sử để dự đoán doanh thu như thể người dùng đã chuyển đổi. Điều này đảm bảo so sánh công bằng giữa các paywall có và không có ưu đãi dùng thử, vì chúng tôi cũng sẽ tính đến các lượt dùng thử đang hoạt động có khả năng mang lại doanh thu trong tương lai.
### Điều gì khác biệt giữa Predicted P2BB và P2BB thông thường? \{#how-is-predicted-p2bb-different-from-just-the-p2bb\}
Các A/B test của chúng tôi sử dụng phương pháp Bayesian: về cơ bản, chúng tôi mô hình hóa phân phối doanh thu trên mỗi người dùng (hay cụ thể hơn là "Doanh thu trên 1K người dùng") rồi tính toán xác suất một phân phối "thực sự" tốt hơn phân phối kia chứ không phải do ngẫu nhiên — và đó là điều chúng tôi gọi là Probability-to-be-the-best hay P2BB (bạn có thể tìm hiểu thêm về cách tiếp cận của chúng tôi [tại đây](maths-behind-it)).
Điều quan trọng cần lưu ý là khi làm như vậy, chúng tôi chỉ dựa vào doanh thu đã tích lũy trong thời gian test chạy. Vì vậy, nếu bạn chạy một test so sánh gói đăng ký hàng năm với gói hàng tuần, bạn sẽ phải chờ rất lâu để thực sự hiểu cái nào hiệu quả hơn. Điều tương tự cũng xảy ra khi bạn so sánh gói đăng ký có dùng thử với gói không có dùng thử trong A/B test — vì các lượt dùng thử đang hoạt động có thể làm thay đổi kết quả người chiến thắng luôn không được tính vào doanh thu.
Đây là lúc mô hình dự đoán của chúng tôi phát huy tác dụng. Dựa trên phân phối doanh thu hiện tại trong A/B test và được huấn luyện trên bộ dữ liệu lớn, mô hình có khả năng dự đoán phiên bản tương lai của phân phối doanh thu (cụ thể là sau 1 năm). Và sau đó, nó tạo ra predicted P2BB — giá trị bạn sẽ đạt được nếu chạy test trong toàn bộ một năm.
Lưu ý rằng đôi khi predicted P2BB có thể mâu thuẫn với P2BB hiện tại. Khi điều đó xảy ra, chúng tôi tô màu vàng các hàng biến thể như sau:
Chúng tôi xem đó là dấu hiệu cho thấy bạn nên tích lũy thêm dữ liệu để xác nhận người chiến thắng hoặc tìm hiểu sâu hơn về A/B test để tìm ra nguyên nhân đằng sau. Nhìn chung, chúng tôi khuyên bạn nên tin tưởng predicted P2BB hơn P2BB hiện tại vì nó đơn giản là tính đến nhiều dữ liệu hơn, nhưng quyết định cuối cùng tất nhiên là tùy bạn.
### Độ chính xác và độ tin cậy của mô hình \{#model-accuracy-and-certainty\}
Mô hình đạt mức độ chính xác cao, với Mean Absolute Percentage Error (MAPE) ở mức dưới 10% một chút. Mức độ chính xác này cho phép các doanh nghiệp tự tin dựa vào các dự đoán của mô hình khi đưa ra quyết định dựa trên dữ liệu.
Để đảm bảo tính ổn định hơn nữa, mô hình sử dụng tiêu chí "độ tin cậy" dựa trên ba yếu tố:
- Khoảng dự đoán hẹp — mô hình tự tin vào kết quả của mình
- Lượng gói đăng ký và doanh thu đủ trong test
- Ít nhất 2 tuần kể từ khi test bắt đầu
Một dự đoán được coi là đáng tin cậy khi ít nhất hai trong ba tiêu chí này được đáp ứng.
Khi một A/B test mới bắt đầu, mô hình cung cấp dự đoán doanh thu trên 1K người dùng trong một năm tới (chỉ số A/B test chính của chúng tôi) cho mỗi paywall. Các dự đoán chỉ được hiển thị khi chúng đáp ứng tiêu chí độ tin cậy. Nếu dữ liệu không đủ, mô hình sẽ hiển thị "insufficient data for prediction".
### Hạn chế và những điều cần lưu ý \{#limitations-and-considerations\}
Mặc dù mô hình dự đoán của chúng tôi là một công cụ mạnh mẽ, nhưng điều quan trọng là phải xem xét các hạn chế của nó.
Hiệu suất của mô hình phụ thuộc vào chất lượng và tính đại diện của dữ liệu có sẵn. Hành vi cohort bất thường hoặc các ứng dụng mới chưa có trong tập huấn luyện có thể ảnh hưởng đến độ chính xác của dự đoán.
Tuy nhiên, các dự đoán được cập nhật hàng ngày để phản ánh dữ liệu và hành vi người dùng mới nhất. Điều này đảm bảo rằng thông tin bạn nhận được luôn dựa trên thông tin cập nhật nhất.
🚧 Lưu ý: Công cụ này là một phần bổ sung, không phải là sự thay thế cho đánh giá chuyên môn và sự hiểu biết của bạn về những đặc thù riêng của ứng dụng. Hãy sử dụng các dự đoán này như một hướng dẫn cùng với các chỉ số khác và kiến thức thị trường để đưa ra quyết định sáng suốt.
---
# File: adapty-ads-manager
---
---
title: "Apple Ads Manager"
description: "Get realtime analytics from Apple Ads and manage and optimize your campaigns"
---
**Apple Ads Manager** is an all-in-one platform designed to help you manage, optimize, and scale your Apple Ads campaigns more efficiently. It connects your Apple Search Ads performance with key revenue metrics such as installs, trials, subscriptions, and lifetime value without requiring an MMP.
With real-time analytics, AI-driven forecasts, and smart automation, Apple Ads Manager eliminates tedious manual bid changes, spreadsheets, and guesswork, and replaces them with clear insights and tools that help you take action faster.
With Apple Ads Manager, you get:
- **[Overview](ads-manager-overview)**: All key metrics at a glance — spend, revenue, ROAS, CPA, and more — each with a daily trend chart
- **Real-time performance data**: Across campaigns, ad groups, and keywords
- **End-to-end revenue tracking**: From search → install → trial → subscription → LTV
- **AI predictions & recommendations**: For profitable scaling
- **Bulk management**: Of bids, budgets, statuses, and structures
- **Rule-based automations**: Manage the full keyword lifecycle
- **[Market Intelligence](ads-manager-market-intelligence)**: Competitor keyword strategies across 50+ countries
- **[CPP A/B Tests](ads-manager-cpp-ab-tests)**: Compare custom product pages head-to-head and find the best performer
### Các sự kiện được hỗ trợ \{#supported-events\}
Theo mặc định, Adapty gửi ba nhóm sự kiện đến User Acquisition:
- Trials
- Gói đăng ký
- Sự cố
Bạn có thể xem danh sách đầy đủ các sự kiện được hỗ trợ [tại đây](events).
## Bước 2. Kết nối nền tảng quảng cáo và thêm tracking link \{#step-2-connect-your-ad-platform-and-add-tracking-links\}
Adapty sử dụng tracking link để kết nối lượt cài đặt ứng dụng với dữ liệu chiến dịch.
Bạn phải sử dụng tracking link làm URL đích trong mỗi chiến dịch quảng cáo mà bạn muốn đo lường trong Adapty UA.
Nếu bạn chạy quảng cáo trên nhiều nền tảng, hãy thiết lập tracking link cho từng nền tảng riêng biệt.
Adapty hoạt động với các nền tảng quảng cáo theo hai cách:
- **Tích hợp gốc (Meta Ads, TikTok Ads).** Adapty kết nối trực tiếp với nền tảng quảng cáo. Tracking link được tạo tự động và các tham số chiến dịch được điền động dựa trên nơi link được sử dụng. Bạn có thể dùng cùng một link cho các chiến dịch, nhóm quảng cáo hoặc creative khác nhau, và Adapty sẽ tự động nhận đúng dữ liệu chiến dịch và chi phí quảng cáo.
- **Chỉ dùng tracking link (tất cả các nền tảng quảng cáo khác).** Adapty không kết nối với nền tảng quảng cáo. Tracking link được tạo thủ công và tất cả các tham số chiến dịch phải được xác định rõ ràng khi tạo link. Dữ liệu chi phí quảng cáo không có sẵn cho các nền tảng này.
3. Trong Policy editor, dán JSON sau và thay `adapty-s3-integration-test` bằng tên bucket của bạn:
```json showLineNumbers title="Json"
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListObjectsInBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::adapty-s3-integration-test"
},
{
"Sid": "AllowAllObjectActions",
"Effect": "Allow",
"Action": "s3:*Object",
"Resource": [
"arn:aws:s3:::adapty-s3-integration-test/*",
"arn:aws:s3:::adapty-s3-integration-test"
]
},
{
"Sid": "AllowBucketLocation",
"Effect": "Allow",
"Action": "s3:GetBucketLocation",
"Resource": "arn:aws:s3:::adapty-s3-integration-test"
}
]
}
```
4. Sau khi hoàn tất cấu hình policy, bạn có thể thêm tags (tùy chọn) rồi nhấn **Next** để chuyển sang bước cuối
5. Ở bước này, bạn đặt tên cho policy và nhấn nút **Create policy** để hoàn tất quá trình tạo
#### 1.2. Tạo IAM user \{#12-create-iam-user\}
Để cho phép Adapty UA tải các báo cáo dữ liệu thô lên bucket của bạn, bạn cần cung cấp Access Key ID và Secret Access Key cho một người dùng có quyền ghi vào bucket cụ thể đó.
1. Truy cập IAM Console và chọn [phần Users](https://console.aws.amazon.com/iamv2/home#/users)
2. Nhấn nút **Add users**
3. Đặt tên cho người dùng, chọn **Access key – Programmatic access**, rồi chuyển sang phần quyền
4. Ở bước tiếp theo, chọn tùy chọn **Add user to group** rồi nhấn nút **Create group**
5. Tiếp theo, đặt tên cho User Group và chọn policy mà bạn đã tạo trước đó
6. Sau khi chọn policy, nhấn nút **Create group** để hoàn tất
7. Sau khi tạo group thành công, hãy **chọn group đó** và tiếp tục bước tiếp theo
8. Đây là bước cuối cùng của phần này, bạn chỉ cần nhấn nút **Create User** để hoàn tất
9. Cuối cùng, bạn có thể **tải thông tin xác thực dưới dạng .csv** hoặc sao chép và dán thông tin xác thực trực tiếp từ dashboard
### Bước 2. Cấu hình tích hợp trong Adapty UA \{#step-2-configure-integration-in-adapty-ua\}
1. Vào [**Integrations** -> **Amazon S3**](https://app.adapty.io/ua/integrations/s3)
2. Bật toggle **Export install events to Amazon S3**.
3. Điền các trường sau để kết nối Amazon S3 với hồ sơ người dùng Adapty UA:
| Trường | Mô tả |
|:-----------------------------| :----------------------------------------------------------- |
| **Access Key ID** | Mã định danh duy nhất dùng để xác thực quyền truy cập của người dùng hoặc ứng dụng vào dịch vụ AWS. Tìm ID này trong [file csv](ua-amazon-s3#step-1-create-amazon-s3-credentials) đã tải về. |
| **Secret Access Key** | Khóa bí mật dùng kết hợp với Access Key ID để xác thực quyền truy cập vào dịch vụ AWS. Tìm khóa này trong [file csv](ua-amazon-s3#step-1-create-amazon-s3-credentials) đã tải về. |
| **S3 Bucket Name** | Tên duy nhất toàn cầu xác định một S3 bucket cụ thể trong AWS cloud. S3 bucket là dịch vụ lưu trữ đơn giản cho phép người dùng lưu trữ và truy xuất các đối tượng dữ liệu như file và hình ảnh trên cloud. |
| **Folder Inside the Bucker** | Tên thư mục bạn muốn tạo bên trong S3 bucket đã chọn. Lưu ý rằng S3 mô phỏng thư mục bằng cách sử dụng tiền tố khóa đối tượng, về cơ bản là tên thư mục. |
| **Region** (Tùy chọn) | Lấy Region của bạn từ AWS Management Console trong tài khoản IAM user. |
## Xuất dữ liệu thủ công \{#manual-data-export\}
Ngoài tính năng tự động xuất dữ liệu sự kiện sang Amazon S3, Adapty UA còn cung cấp chức năng xuất file thủ công. Với tính năng này, bạn có thể chọn một ngày cụ thể để lấy dữ liệu thu hút người dùng và xuất sang S3 bucket theo cách thủ công. Điều này giúp bạn kiểm soát tốt hơn dữ liệu cần xuất và thời điểm xuất.
## Cấu trúc bảng \{#table-structure\}
Trong tích hợp AWS S3, Adapty UA cung cấp một bảng để lưu trữ dữ liệu lịch sử cho các sự kiện cài đặt. Bảng chứa thông tin về hồ sơ người dùng, doanh thu và lợi nhuận, cửa hàng gốc và nhiều điểm dữ liệu khác.
:::warning
Lưu ý rằng cấu trúc này có thể phát triển theo thời gian — với dữ liệu mới được chúng tôi hoặc các bên thứ ba mà chúng tôi hợp tác giới thiệu. Hãy đảm bảo rằng code xử lý dữ liệu của bạn đủ linh hoạt và dựa vào các trường cụ thể, không phụ thuộc vào toàn bộ cấu trúc.
:::
Dưới đây là cấu trúc bảng cho các sự kiện:
| Cột | Mô tả |
|--------------------------|-------------------------------------------|
| `adapty_profile_id` | Mã định danh hồ sơ người dùng Adapty duy nhất |
| `install_id` | Mã định danh cài đặt duy nhất |
| `created_at` | Timestamp tạo bản ghi (ISO 8601) |
| `installed_at` | Timestamp cài đặt ứng dụng (ISO 8601) |
| `store` | Cửa hàng ứng dụng (`ios`, `android`) |
| `country` | Mã quốc gia của người dùng (ISO 3166-1 alpha-2) |
| `ip_address` | Địa chỉ IP của client |
| `idfa` | iOS Identifier for Advertisers |
| `idfv` | iOS Identifier for Vendors |
| `gaid` | Google Advertising ID (Android) |
| `android_id` | ID thiết bị Android |
| `app_set_id` | Android App Set ID |
| `channel` | Kênh attribution |
| `campaign_id` | Mã định danh chiến dịch |
| `campaign_name` | Tên chiến dịch |
| `adset_id` | Mã định danh ad set |
| `adset_name` | Tên ad set |
| `ad_id` | Mã định danh quảng cáo |
| `ad_name` | Tên quảng cáo |
| `keyword_id` | Mã định danh từ khóa |
| `keyword_name` | Tên từ khóa |
| `asa_org_id` | ID tổ chức Apple Search Ads |
| `asa_keyword_match_type` | Loại khớp từ khóa ASA (`Exact`, `Broad`) |
| `asa_attribution` | Dữ liệu attribution ASA (chuỗi JSON) |
| `asa_conversion_type` | Loại chuyển đổi ASA |
| `asa_country_or_region` | Quốc gia hoặc khu vực ASA |
| `asa_creative_set_name` | Tên creative set ASA |
| `fbclid` | Facebook Click ID |
| `ttclid` | TikTok Click ID |
| `utm_source` | Tham số UTM source |
| `utm_medium` | Tham số UTM medium |
| `utm_campaign` | Tham số UTM campaign |
| `utm_term` | Tham số UTM term |
| `utm_content` | Tham số UTM content |
---
# File: ua-google-cloud-storage
---
---
title: "Google Cloud Storage"
description: "Tích hợp Google Cloud Storage với Adapty UA để lưu trữ dữ liệu thu hút người dùng một cách an toàn."
---
Tích hợp Adapty UA với Google Cloud Storage cho phép bạn lưu trữ dữ liệu chiến dịch thu hút người dùng một cách an toàn tại một nơi tập trung. Bạn có thể lưu dữ liệu hiệu suất chiến dịch, dữ liệu attribution và các sự kiện thu hút người dùng vào bucket Google Cloud Storage của mình dưới dạng file .csv.
Để thiết lập tích hợp này, bạn cần thực hiện một vài bước đơn giản trong Google Cloud Console và Adapty UA Dashboard.
:::note
Lịch trình
Adapty UA gửi dữ liệu của bạn đến Google Cloud Storage mỗi 24h vào lúc 4:00 UTC.
Mỗi file sẽ chứa dữ liệu cho các sự kiện được tạo trong toàn bộ ngày lịch trước đó theo UTC. Ví dụ: dữ liệu được xuất tự động lúc 4:00 UTC ngày 8 tháng 3 sẽ chứa tất cả các sự kiện được tạo vào ngày 7 tháng 3 từ 00:00:00 đến 23:59:59 theo UTC.
:::
## Cách thiết lập tích hợp Google Cloud Storage \{#how-to-set-up-google-cloud-storage-integration\}
### Bước 1. Tạo thông tin xác thực Google Cloud Storage \{#step-1-create-google-cloud-storage-credentials\}
Hướng dẫn này sẽ giúp bạn tạo các thông tin xác thực cần thiết trong Google Cloud Platform Console.
Để Adapty UA có thể tải các báo cáo dữ liệu thô lên bucket của bạn, cần có khóa của service account cũng như quyền ghi vào bucket tương ứng. Bằng cách cung cấp khóa service account và cấp quyền ghi vào bucket, bạn cho phép Adapty UA chuyển dữ liệu thô từ nền tảng của mình sang môi trường lưu trữ của bạn một cách an toàn và hiệu quả.
:::warning
Lưu ý rằng chúng tôi chỉ hỗ trợ xác thực bằng Service Account HMAC key, do đó cần đảm bảo rằng Service Account HMAC key của bạn có các vai trò "Storage Object Viewer", "Storage Legacy Bucket Writer" và "Storage Object Creator" để cho phép truy cập đúng vào Google Cloud Storage.
:::
#### 2.1. Tạo Service Account \{#21-create-service-account\}
1. Truy cập phần [IAM](https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts) trong tài khoản Google Cloud của bạn và chọn dự án liên quan hoặc tạo dự án mới
2. Tiếp theo, tạo service account mới cho Adapty UA bằng cách nhấp vào nút "+ CREATE SERVICE ACCOUNT"
3. Điền vào các trường ở bước đầu tiên, vì quyền truy cập sẽ được cấp ở giai đoạn sau. Để đọc thêm chi tiết về trang này, hãy xem tài liệu [tại đây](https://docs.cloud.google.com/iam/docs/service-accounts-create)
4. Để tạo và tải xuống [khóa JSON riêng tư](https://docs.cloud.google.com/iam/docs/keys-create-delete), điều hướng đến phần KEYS và nhấp vào nút "ADD KEY"
5. Trong phần DETAILS, tìm giá trị Email liên kết với service account vừa tạo và sao chép lại. Thông tin này sẽ cần thiết cho các bước tiếp theo để ủy quyền cho tài khoản và cho phép nó ghi vào bucket
#### 2.2. Cấu hình quyền cho Bucket \{#22-configure-bucket-permissions\}
6. Truy cập trang [Buckets](https://console.cloud.google.com/storage/browser) của Google Cloud Storage và chọn bucket hiện có hoặc tạo bucket mới để lưu trữ các báo cáo User Acquisition Data từ Adapty UA
7. Điều hướng đến phần PERMISSIONS và chọn tùy chọn [GRANT ACCESS](https://docs.cloud.google.com/identity/docs/how-to?hl=en)
8. Trong phần PERMISSIONS, nhập Email của service account đã lấy ở bước thứ năm ở trên, sau đó chọn vai trò Storage Object Creator
9. Cuối cùng, nhấp vào SAVE để áp dụng các thay đổi
10. Hãy nhớ lưu tên bucket để tham khảo sau
11. Sau khi hoàn thành các bước này, bạn đã thiết lập thành công các bước cần thiết trong Google Cloud Console! Bước cuối cùng là nhập tên bucket và tải xuống file JSON để sử dụng trong Adapty UA
### Bước 2. Cấu hình tích hợp trong Adapty UA \{#step-2-configure-integration-in-adapty-ua\}
1. Truy cập [**Integrations** -> **Google Cloud Storage**](https://app.adapty.io/ua/integrations/google-cloud-storage)
2. Bật toggle **Export install events to Google Cloud Storage**
3. Điền vào các trường bắt buộc để thiết lập kết nối giữa Google Cloud Storage và Adapty UA:
| Trường | Mô tả |
|:------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Google Cloud service account key file** | File khóa [JSON](ua-google-cloud-storage#step-1-create-google-cloud-storage-credentials) riêng tư đã tải xuống. |
| **Google Cloud bucket name** | Tên bucket trong Google Cloud Storage nơi bạn muốn lưu trữ dữ liệu. Tên này phải duy nhất trong môi trường Google Cloud Storage và không được chứa khoảng trắng. |
| **Folder inside the bucket** | Tên thư mục bên trong bucket nơi bạn muốn lưu trữ dữ liệu. Tên này phải duy nhất trong bucket và có thể dùng để sắp xếp dữ liệu. Trường này không bắt buộc điền. |
## Xuất dữ liệu thủ công \{#manual-data-export\}
Ngoài tính năng tự động xuất dữ liệu sự kiện sang Google Cloud Storage, Adapty UA còn cung cấp tính năng xuất file thủ công. Với tính năng này, bạn có thể chọn một ngày cụ thể cho dữ liệu thu hút người dùng và xuất thủ công sang bucket GCS của mình. Điều này giúp bạn kiểm soát tốt hơn dữ liệu cần xuất và thời điểm xuất.
## Cấu trúc bảng \{#table-structure\}
Trong tích hợp Google Cloud Storage, Adapty UA cung cấp một bảng để lưu trữ dữ liệu lịch sử cho các sự kiện cài đặt. Bảng chứa thông tin về hồ sơ người dùng, doanh thu và thu nhập, cửa hàng xuất xứ, cùng các điểm dữ liệu khác.
:::warning
Lưu ý rằng cấu trúc này có thể mở rộng theo thời gian — khi có dữ liệu mới được chúng tôi hoặc các bên thứ ba chúng tôi hợp tác giới thiệu. Hãy đảm bảo rằng code xử lý dữ liệu của bạn đủ linh hoạt và dựa trên các trường cụ thể, không phụ thuộc vào toàn bộ cấu trúc.
:::
Dưới đây là cấu trúc bảng cho các sự kiện:
| Cột | Mô tả |
|--------------------------|-----------------------------------------------------------|
| `adapty_profile_id` | Mã định danh hồ sơ Adapty duy nhất |
| `install_id` | Mã định danh cài đặt duy nhất |
| `created_at` | Thời điểm tạo bản ghi (ISO 8601) |
| `installed_at` | Thời điểm cài đặt ứng dụng (ISO 8601) |
| `store` | Cửa hàng ứng dụng (`ios`, `android`) |
| `country` | Mã quốc gia của người dùng (ISO 3166-1 alpha-2) |
| `ip_address` | Địa chỉ IP của client |
| `idfa` | iOS Identifier for Advertisers |
| `idfv` | iOS Identifier for Vendors |
| `gaid` | Google Advertising ID (Android) |
| `android_id` | ID thiết bị Android |
| `app_set_id` | Android App Set ID |
| `channel` | Kênh attribution |
| `campaign_id` | Mã định danh chiến dịch |
| `campaign_name` | Tên chiến dịch |
| `adset_id` | Mã định danh ad set |
| `adset_name` | Tên ad set |
| `ad_id` | Mã định danh quảng cáo |
| `ad_name` | Tên quảng cáo |
| `keyword_id` | Mã định danh từ khóa |
| `keyword_name` | Tên từ khóa |
| `asa_org_id` | ID tổ chức Apple Search Ads |
| `asa_keyword_match_type` | Kiểu khớp từ khóa ASA (`Exact`, `Broad`) |
| `asa_attribution` | Dữ liệu attribution ASA (chuỗi JSON) |
| `asa_conversion_type` | Loại chuyển đổi ASA |
| `asa_country_or_region` | Quốc gia hoặc khu vực ASA |
| `asa_creative_set_name` | Tên creative set ASA |
| `fbclid` | Facebook Click ID |
| `ttclid` | TikTok Click ID |
| `utm_source` | Tham số UTM source |
| `utm_medium` | Tham số UTM medium |
| `utm_campaign` | Tham số UTM campaign |
| `utm_term` | Tham số UTM term |
| `utm_content` | Tham số UTM content |
---
# File: adapty-mail
---
---
title: "Adapty Mail"
description: "AI-generated email campaigns that turn trial users into paid subscribers."
---
Các tích hợp cung cấp các tùy chọn cấu hình sau đây, ảnh hưởng đến tất cả các sự kiện được gửi qua tích hợp này:
| Cài đặt | Mô tả |
|:--------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Reporting Proceeds** | Chọn cách hiển thị giá trị doanh thu: thuần sau khi trừ hoa hồng của App Store và Play Store, hoặc tổng (trước khi khấu trừ). Bật checkbox "Send sales as proceeds" để hiển thị doanh thu sau khi đã trừ hoa hồng của App Store / Play Store. |
| **Send Trial Price** | Nếu được chọn, Adapty sẽ truyền giá gói đăng ký cho sự kiện Trial Started. |
| **Exclude Historical Events** | Chọn để loại trừ các sự kiện xảy ra trước khi người dùng cài đặt ứng dụng có tích hợp Adapty SDK. Điều này giúp tránh trùng lặp sự kiện và đảm bảo báo cáo chính xác. Ví dụ: nếu người dùng kích hoạt gói đăng ký hàng tháng vào ngày 10 tháng 1 và cập nhật ứng dụng có Adapty SDK vào ngày 6 tháng 3, Adapty sẽ bỏ qua các sự kiện trước ngày 6 tháng 3 và giữ lại các sự kiện sau đó. |
| **Report User's Currency** | Chọn xem doanh thu được báo cáo theo đơn vị tiền tệ của người dùng hay USD. |
| **Send User Attributes** | Nếu bạn muốn gửi các thuộc tính riêng của người dùng như tùy chọn ngôn ngữ, và gói OneSignal của bạn hỗ trợ hơn 10 tag, hãy chọn tùy chọn này. Bật tùy chọn này cho phép bao gồm thông tin bổ sung ngoài 10 tag mặc định. Lưu ý rằng vượt quá giới hạn tag có thể gây ra lỗi. |
| **Send Attributions** | Bật tùy chọn này để truyền thông tin attribution (ví dụ: attribution từ AppsFlyer) và nhận các thông tin liên quan. |
| **Send Play Store purchase token** | Bật tùy chọn này để nhận token Play Store cần thiết để xác thực lại giao dịch mua nếu cần. Tùy chọn này sẽ thêm tham số `play_store_purchase_token` vào sự kiện. |
| **Delay events with future datetime** | **Chỉ dành cho AppsFlyer và custom webhook**: Khi bật, các sự kiện gia hạn và chuyển đổi dùng thử sẽ được gửi vào ngày chúng thực sự xảy ra. Khi tắt (mặc định), các sự kiện này được gửi ngay khi phát hiện, ngay cả khi ngày đó là trong tương lai. |
| **Data residency** | **Chỉ dành cho Mixpanel và Amplitude**: Chọn data residency để xác định nơi các sự kiện của bạn được xử lý và lưu trữ. |
## Cấu hình sự kiện \{#configure-the-events\}
Bên dưới phần thông tin xác thực, có ba nhóm sự kiện bạn có thể gửi tới nền tảng tích hợp đã chọn từ Adapty. Bạn nên bật những sự kiện mà bạn cần.
Lưu ý rằng việc tùy chỉnh tên sự kiện chỉ khả dụng với một số tích hợp nhất định, trong khi với các tích hợp khác, tên sự kiện đã được đặt sẵn và không thể thay đổi. Ngoài ra, với một số tích hợp như [Airbridge](airbridge#configure-events-and-tags) chẳng hạn, bạn có thể linh hoạt gán nhiều tên sự kiện cho một sự kiện Adapty duy nhất. Xem danh sách đầy đủ các sự kiện mà Adapty cung cấp [tại đây](events).
Mặc dù chúng tôi khuyến nghị sử dụng tên sự kiện mặc định của Adapty, bạn vẫn hoàn toàn có thể tùy chỉnh tên sự kiện theo yêu cầu cụ thể của mình.
---
# File: events
---
---
title: "Sự kiện gửi đến tích hợp bên thứ ba"
description: "Theo dõi các sự kiện gói đăng ký quan trọng bằng các công cụ phân tích của Adapty."
---
Apple và Google gửi các sự kiện gói đăng ký trực tiếp đến máy chủ thông qua [App Store Server Notifications](enable-app-store-server-notifications) và [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn). Do đó, ứng dụng di động không thể gửi sự kiện đến các hệ thống phân tích theo thời gian thực một cách đáng tin cậy. Chẳng hạn, nếu người dùng đăng ký nhưng không bao giờ mở lại ứng dụng, nhà phát triển sẽ không nhận được bất kỳ cập nhật trạng thái gói đăng ký nào nếu không có máy chủ.
Adapty lấp đầy khoảng trống này bằng cách thu thập dữ liệu gói đăng ký và chuyển đổi thành các sự kiện dễ đọc. Các sự kiện tích hợp này được gửi ở định dạng JSON. Mặc dù tất cả sự kiện có cùng cấu trúc, các trường của chúng khác nhau tùy theo loại sự kiện, cửa hàng và cấu hình cụ thể. Bạn có thể tìm thấy các trường chính xác trong từng sự kiện trên các trang tích hợp tương ứng.
Để hiểu cách xác định xem một sự kiện đã được xử lý thành công hay có sự cố gì đó, hãy xem trang [Trạng thái sự kiện](event-statuses).
## Các loại sự kiện \{#event-types\}
Hầu hết các sự kiện đều được tạo và gửi đến tất cả các tích hợp đã cấu hình nếu chúng được bật. Tuy nhiên, sự kiện **Access level updated** chỉ kích hoạt nếu [tích hợp webhook](webhook) được cấu hình và sự kiện này được bật. Sự kiện này sẽ xuất hiện trong [Event Feed](https://app.adapty.io/event-feed) và cũng sẽ được gửi đến webhook, nhưng sẽ không được chia sẻ với các tích hợp khác.
Nếu tích hợp webhook chưa được cấu hình hoặc loại sự kiện này chưa được bật, sự kiện **Access level updated** sẽ không được tạo và sẽ không xuất hiện trong [Event Feed](https://app.adapty.io/event-feed).
| Tên sự kiện | Mô tả |
|:-----------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| subscription_started | Kích hoạt khi người dùng bắt đầu gói đăng ký trả phí không có thời gian dùng thử, tức là bị tính phí ngay lập tức. |
| subscription_renewed | Xảy ra khi gói đăng ký được gia hạn và người dùng bị tính phí. Sự kiện này bắt đầu từ lần thanh toán thứ hai, dù là gói đăng ký có hay không có dùng thử. |
| subscription_renewal_cancelled | Người dùng đã tắt tính năng tự động gia hạn gói đăng ký. Người dùng vẫn có thể sử dụng các tính năng cao cấp cho đến khi kết thúc chu kỳ đăng ký đã thanh toán. |
| subscription_renewal_reactivated | Kích hoạt khi người dùng bật lại tính năng tự động gia hạn gói đăng ký. |
| subscription_expired | Kích hoạt khi gói đăng ký hết hạn hoàn toàn sau khi bị hủy. Ví dụ: nếu người dùng hủy gói đăng ký vào ngày 12 tháng 12 nhưng vẫn còn hiệu lực đến ngày 31 tháng 12, sự kiện sẽ được ghi nhận vào ngày 31 tháng 12 khi gói đăng ký hết hạn. |
| subscription_paused | Xảy ra khi người dùng kích hoạt tính năng [tạm dừng gói đăng ký](https://developer.android.com/google/play/billing/lifecycle/subscriptions#pause) (chỉ dành cho Android). |
| subscription_deferred | Kích hoạt khi giao dịch mua gói đăng ký được [hoãn lại](https://adapty.io/glossary/subscription-purchase-deferral/), cho phép người dùng trì hoãn việc thanh toán trong khi vẫn duy trì quyền truy cập các tính năng cao cấp. Tính năng này có sẵn thông qua Google Play Developer API và có thể dùng cho các bản dùng thử miễn phí hoặc hỗ trợ người dùng gặp khó khăn về tài chính. |
| non_subscription_purchase | Bất kỳ sản phẩm mua một lần nào, chẳng hạn như quyền truy cập trọn đời hoặc các sản phẩm consumable như tiền trong game. |
| trial_started | Kích hoạt khi người dùng bắt đầu gói đăng ký dùng thử. |
| trial_converted | Xảy ra khi thời gian dùng thử kết thúc và người dùng bị tính phí (lần mua đầu tiên). Ví dụ: nếu người dùng có thời gian dùng thử đến ngày 14 tháng 1 nhưng bị tính phí vào ngày 7 tháng 1, sự kiện sẽ được ghi nhận vào ngày 7 tháng 1. |
| trial_renewal_cancelled | Người dùng đã tắt tính năng tự động gia hạn trong thời gian dùng thử. Người dùng vẫn có thể sử dụng các tính năng cao cấp cho đến khi thời gian dùng thử kết thúc nhưng sẽ không bị tính phí hay chuyển sang gói đăng ký. |
| trial_renewal_reactivated | Xảy ra khi người dùng bật lại tính năng tự động gia hạn trong thời gian dùng thử. |
| trial_expired | Kích hoạt khi thời gian dùng thử kết thúc mà không chuyển sang gói đăng ký. |
| entered_grace_period | Xảy ra khi một lần thanh toán thất bại và người dùng bước vào thời gian ân hạn (nếu được bật). Người dùng vẫn có thể truy cập các tính năng cao cấp trong thời gian này. |
| billing_issue_detected | Kích hoạt khi xảy ra sự cố thanh toán trong quá trình thực hiện giao dịch (ví dụ: số dư thẻ không đủ). |
| subscription_refunded | Kích hoạt khi gói đăng ký được hoàn tiền (ví dụ: do Apple Support xử lý). |
| non_subscription_purchase_refunded | Kích hoạt khi một sản phẩm mua một lần được hoàn tiền. |
| access_level_updated | Xảy ra khi mức độ truy cập của người dùng được cập nhật. |
Các sự kiện trên bao quát đầy đủ trạng thái của người dùng trong quá trình mua hàng. Hãy cùng xem một số ví dụ.
### Ví dụ 1 \{#example-1\}
_Người dùng kích hoạt gói đăng ký hàng tháng vào ngày 1 tháng 4 với thời gian dùng thử 7 ngày. Vào ngày thứ 4, anh ta hủy đăng ký._
Trong trường hợp đó, các sự kiện sau sẽ được gửi:
1. `trial_started` vào ngày 1 tháng 4
2. `trial_renewal_cancelled` vào ngày 4 tháng 4
3. `trial_expired` vào ngày 7 tháng 4
### Ví dụ 2 \{#example-2\}
_Người dùng kích hoạt gói đăng ký hàng tháng vào ngày 1 tháng 4 với thời gian dùng thử 7 ngày. Vào ngày thứ 10, anh ta hủy đăng ký._
Trong trường hợp đó, các sự kiện sau sẽ được gửi:
1. `trial_started` vào ngày 1 tháng 4
2. `trial_converted` vào ngày 7 tháng 4
3. `subscription_renewal_cancelled` vào ngày 10 tháng 4
4. `subscription_expired` vào ngày 1 tháng 5
Để xem chi tiết các sự kiện nào được kích hoạt trong từng tình huống, hãy xem [Luồng sự kiện](event-flows).
---
# File: event-flows
---
---
title: "Luồng sự kiện"
description: "Khám phá các sơ đồ chi tiết về luồng sự kiện gói đăng ký trong Adapty. Tìm hiểu cách các sự kiện gói đăng ký được tạo ra và gửi đến các tích hợp, giúp bạn theo dõi các thời điểm quan trọng trong hành trình của khách hàng."
---
Trong Adapty, bạn sẽ nhận được nhiều sự kiện gói đăng ký khác nhau trong suốt hành trình của khách hàng trong ứng dụng. Các luồng gói đăng ký này phác thảo các kịch bản phổ biến để giúp bạn hiểu các sự kiện mà Adapty tạo ra khi người dùng đăng ký, hủy hoặc kích hoạt lại gói đăng ký.
Lưu ý rằng Apple xử lý thanh toán gói đăng ký vài giờ trước thời điểm bắt đầu/gia hạn thực tế. Trong các luồng dưới đây, chúng tôi hiển thị cả việc bắt đầu/gia hạn gói đăng ký và việc tính phí xảy ra cùng một lúc để sơ đồ dễ đọc hơn.
Ngoài ra, các sự kiện liên quan đến cùng một hành động xảy ra đồng thời và có thể xuất hiện trong **Event Feed** của bạn theo bất kỳ thứ tự nào, có thể khác với trình tự được hiển thị trong sơ đồ của chúng tôi.
## Vòng đời gói đăng ký \{#subscription-lifecycle\}
### Luồng mua lần đầu \{#initial-purchase-flow\}
Luồng này xảy ra khi khách hàng mua gói đăng ký lần đầu tiên mà không có dùng thử. Trong trường hợp này, các sự kiện sau được tạo ra:
- **Subscription started**
- **Access level updated** để cấp quyền truy cập cho người dùng
Khi đến ngày gia hạn gói đăng ký, gói đăng ký sẽ được gia hạn. Trong trường hợp này, các sự kiện sau được tạo ra:
- **Subscription renewal** để bắt đầu một kỳ mới của gói đăng ký
- **Access level updated** để cập nhật ngày hết hạn gói đăng ký, kéo dài quyền truy cập thêm một kỳ nữa
Các tình huống khi thanh toán không thành công hoặc khi người dùng hủy gia hạn được mô tả trong [Luồng kết quả sự cố thanh toán](event-flows#billing-issue-outcome-flow) và [Luồng hủy gói đăng ký](event-flows#subscription-cancellation-flow) tương ứng.
### Luồng hủy gói đăng ký \{#subscription-cancellation-flow\}
Khi người dùng hủy gói đăng ký của họ, các sự kiện sau được tạo ra:
- **Subscription renewal canceled** để chỉ ra rằng gói đăng ký vẫn còn hiệu lực đến cuối kỳ hiện tại, sau đó người dùng sẽ mất quyền truy cập
- Sự kiện **Access level updated** được tạo ra để tắt tự động gia hạn cho mức độ truy cập
Khi gói đăng ký kết thúc, sự kiện **Subscription expired (churned)** được kích hoạt để đánh dấu kết thúc gói đăng ký.
Nếu yêu cầu hoàn tiền được chấp thuận, sự kiện sau sẽ thay thế **Subscription expired (churned)**:
- **Subscription refunded** để kết thúc gói đăng ký và cung cấp thông tin chi tiết về việc hoàn tiền
Đối với Stripe, gói đăng ký có thể bị hủy ngay lập tức, bỏ qua khoảng thời gian còn lại. Trong trường hợp này, tất cả các sự kiện được tạo ra đồng thời:
- **Subscription renewal cancelled**
- **Subscription expired (churned)**
- **Access Level updated** để xóa quyền truy cập của người dùng
Nếu yêu cầu hoàn tiền được chấp thuận, sự kiện **Subscription refunded** cũng được kích hoạt khi được phê duyệt.
### Luồng kích hoạt lại gói đăng ký \{#subscription-reactivation-flow\}
Nếu người dùng hủy gói đăng ký, gói đó hết hạn và sau đó họ mua lại cùng gói đăng ký đó, sự kiện **Subscription renewed** sẽ được tạo ra. Ngay cả khi có khoảng thời gian gián đoạn quyền truy cập, Adapty xử lý đây là một chuỗi giao dịch duy nhất, được liên kết bởi `vendor_original_transaction_id`. Vì vậy, việc mua lại được coi là một lần gia hạn.
Các sự kiện **Access level updated** sẽ được tạo ra hai lần:
- vào lúc kết thúc gói đăng ký để thu hồi quyền truy cập của người dùng
- vào lúc mua lại gói đăng ký để cấp quyền truy cập
### Luồng tạm dừng gói đăng ký (chỉ Android) \{#subscription-pause-flow-android-only\}
Luồng này áp dụng khi người dùng tạm dừng và sau đó tiếp tục gói đăng ký trên Android.
Việc tạm dừng gói đăng ký có hiệu lực trì hoãn. Nếu người dùng tạm dừng gói đăng ký trước khi đến ngày gia hạn, gói đăng ký vẫn còn hiệu lực và người dùng vẫn được truy cập trong phần còn lại của kỳ thanh toán.
1. Khi người dùng tạm dừng gói đăng ký, họ kích hoạt sự kiện **Subscription paused (Android only)**.
2. Vào cuối kỳ gói đăng ký, Adapty kích hoạt sự kiện **Access level updated** để thu hồi quyền truy cập của người dùng.
3. Khi người dùng tiếp tục gói đăng ký, các sự kiện sau được kích hoạt:
- **Subscription renewed**
- **Access level updated** để khôi phục quyền truy cập của người dùng
Các gói đăng ký này sẽ thuộc cùng một chuỗi giao dịch, được liên kết với cùng **vendor_original_transaction_id**.
## Luồng dùng thử \{#trial-flows\}
Nếu bạn sử dụng tính năng dùng thử trong ứng dụng, bạn sẽ nhận được các sự kiện bổ sung liên quan đến dùng thử.
### Luồng dùng thử với chuyển đổi thành công \{#trial-with-successful-conversion-flow\}
Luồng phổ biến nhất xảy ra khi người dùng bắt đầu dùng thử, cung cấp thẻ tín dụng và chuyển đổi thành công sang gói đăng ký tiêu chuẩn vào cuối kỳ dùng thử. Trong trường hợp này, các sự kiện sau được tạo ra tại thời điểm bắt đầu dùng thử:
- **Trial started** để đánh dấu thời điểm bắt đầu dùng thử
- **Access level updated** để cấp quyền truy cập
Sự kiện **Trial converted** được tạo ra khi gói đăng ký tiêu chuẩn bắt đầu.
### Luồng dùng thử không chuyển đổi thành công \{#trial-without-successful-conversion-flow\}
Nếu người dùng hủy dùng thử trước khi chuyển đổi sang gói đăng ký, các sự kiện sau được tạo ra tại thời điểm hủy:
- **Trial renewal cancelled** để tắt chuyển đổi tự động từ dùng thử sang gói đăng ký
- **Access level updated** để tắt gia hạn quyền truy cập
Người dùng sẽ có quyền truy cập cho đến khi kết thúc kỳ dùng thử, khi đó sự kiện **Trial expired** được tạo ra để đánh dấu kết thúc dùng thử.
### Luồng kích hoạt lại gói đăng ký sau khi dùng thử hết hạn \{#subscription-reactivation-after-expired-trial-flow\}
Nếu dùng thử hết hạn (do sự cố thanh toán hoặc hủy) và người dùng sau đó mua gói đăng ký, các sự kiện sau được tạo ra:
- **Access level updated** để cấp quyền truy cập cho người dùng
- **Trial converted**
Ngay cả khi có khoảng thời gian gián đoạn giữa dùng thử và gói đăng ký, Adapty liên kết hai sự kiện này bằng `vendor_original_transaction_id`. Việc chuyển đổi này được coi là một phần của chuỗi giao dịch liên tục, bắt đầu bằng dùng thử miễn phí. Đó là lý do tại sao sự kiện **Trial converted** được tạo ra thay vì **Subscription started**.
## Thay đổi sản phẩm \{#product-changes\}
Phần này đề cập đến mọi điều chỉnh đối với gói đăng ký đang hoạt động, chẳng hạn như nâng cấp, hạ cấp hoặc mua sản phẩm từ nhóm khác.
### Luồng thay đổi sản phẩm ngay lập tức \{#immediate-product-change-flow\}
Sau khi người dùng thay đổi sản phẩm, nó có thể được thay đổi trong hệ thống ngay lập tức trước khi gói đăng ký kết thúc (chủ yếu trong trường hợp nâng cấp hoặc thay thế sản phẩm). Trong trường hợp này, tại thời điểm thay đổi sản phẩm:
- Mức độ truy cập được thay đổi và hai sự kiện **Access level updated** được tạo ra:
1. để xóa quyền truy cập vào sản phẩm đầu tiên.
2. để cấp quyền truy cập vào sản phẩm thứ hai.
- Gói đăng ký cũ kết thúc và hoàn tiền được thực hiện (sự kiện **Subscription refunded** được tạo ra với `cancellation_reason` = `upgraded`). Lưu ý rằng không có sự kiện **Subscription expired (churned)** nào được tạo ra; sự kiện **Subscription refunded** thay thế nó.
- Gói đăng ký mới bắt đầu (sự kiện **Subscription started** được tạo ra cho sản phẩm mới).
Nếu người dùng hạ cấp gói đăng ký, gói đăng ký đầu tiên sẽ kéo dài đến cuối kỳ đã thanh toán, và khi gói đăng ký kết thúc, nó sẽ được thay thế bằng gói đăng ký mới ở cấp thấp hơn. Trong trường hợp này, chỉ có sự kiện **Access level updated** để tắt tự động gia hạn quyền truy cập được tạo ra ngay lập tức. Tất cả các sự kiện khác sẽ được tạo ra tại thời điểm thực tế thay thế gói đăng ký:
- Một sự kiện **Access level updated** khác được tạo ra để cấp quyền truy cập vào sản phẩm thứ hai.
- Sự kiện **Subscription expired (churned)** được tạo ra để kết thúc gói đăng ký cho sản phẩm đầu tiên.
- Sự kiện **Subscription started** được tạo ra để bắt đầu gói đăng ký mới cho sản phẩm mới.
### Luồng thay đổi sản phẩm trì hoãn \{#delayed-product-change-flow\}
Cũng có một biến thể khi người dùng thay đổi sản phẩm vào thời điểm gia hạn gói đăng ký. Biến thể này rất giống với biến thể trước: một sự kiện **Access level updated** sẽ được tạo ra ngay lập tức để tắt tự động gia hạn quyền truy cập cho sản phẩm cũ. Tất cả các sự kiện khác sẽ được tạo ra tại thời điểm người dùng thay đổi gói đăng ký và nó được thay đổi trong hệ thống:
- Một sự kiện **Access level updated** khác được tạo ra để cấp quyền truy cập vào sản phẩm thứ hai.
- Sự kiện **Subscription expired (churned)** được tạo ra để kết thúc gói đăng ký cho sản phẩm đầu tiên.
- Sự kiện **Subscription started** được tạo ra để bắt đầu gói đăng ký mới cho sản phẩm mới.
## Luồng kết quả sự cố thanh toán \{#billing-issue-outcome-flow\}
Nếu các lần thử chuyển đổi dùng thử hoặc gia hạn gói đăng ký thất bại do sự cố thanh toán, những gì xảy ra tiếp theo phụ thuộc vào việc thời gian ân hạn có được bật hay không.
Với thời gian ân hạn, nếu thanh toán thành công, dùng thử sẽ được chuyển đổi hoặc gói đăng ký được gia hạn. Nếu thất bại, cửa hàng ứng dụng sẽ tiếp tục cố gắng tính phí người dùng cho gói đăng ký và nếu vẫn thất bại, cửa hàng ứng dụng sẽ tự kết thúc dùng thử hoặc gói đăng ký.
Do đó, tại thời điểm xảy ra sự cố thanh toán, các sự kiện sau được tạo ra trong Adapty:
- **Billing issue detected**
- **Entered grace period** (nếu thời gian ân hạn được bật)
- **Access level updated** để cấp quyền truy cập đến cuối thời gian ân hạn
Nếu thanh toán thành công sau đó, Adapty ghi lại sự kiện **Trial converted** hoặc **Subscription renewed**, và người dùng không mất quyền truy cập.
Nếu thanh toán cuối cùng thất bại và cửa hàng ứng dụng hủy gói đăng ký, Adapty tạo ra các sự kiện sau:
- **Trial expired** hoặc **Subscription expired (churned)** với `cancellation_reason: billing_error`
- **Access level updated** để thu hồi quyền truy cập của người dùng
Không có thời gian ân hạn, Thời gian thử lại thanh toán (khoảng thời gian cửa hàng ứng dụng tiếp tục cố gắng tính phí người dùng) bắt đầu ngay lập tức.
Nếu thanh toán không bao giờ thành công đến cuối thời gian ân hạn, luồng vẫn giống nhau: các sự kiện tương tự được tạo ra khi cửa hàng ứng dụng tự động kết thúc gói đăng ký:
- Sự kiện **Trial expired** hoặc **Subscription expired (churned)** với `cancellation_reason` là `billing_error`
- **Access level updated** để thu hồi quyền truy cập của người dùng
## Luồng chia sẻ giao dịch mua giữa các tài khoản người dùng \{#sharing-purchases-across-user-accounts-flows\}
Khi một
Đây là phân tích chi tiết các trường liên quan đến gán mức độ truy cập và chuyển nhượng trong các sự kiện được tạo ra trong kịch bản này:
- **User A: Access level updated (được gửi khi User A mua gói đăng ký trong ứng dụng)**
```json showLineNumbers
{
"profile_id": "00000000-0000-0000-0000-000000000000",
"customer_user_id": UserA,
"event_properties": {
"profile_has_access_level": true,
},
"profiles_sharing_access_level": null
}
```
- **User A: Access level updated (được gửi khi ứng dụng được cài đặt lại và User B đăng nhập, thu hồi quyền truy cập của User A)**
```json showLineNumbers
{
"profile_id": "00000000-0000-0000-0000-000000000000",
"customer_user_id": UserA,
"event_properties": {
"profile_has_access_level": false,
},
"profiles_sharing_access_level": null
}
```
- **User B: Access level updated (được gửi khi User B đăng nhập và quyền truy cập được cấp)**
```json showLineNumbers
{
"profile_id": "00000000-0000-0000-0000-000000000001",
"customer_user_id": UserB,
"event_properties": {
"profile_has_access_level": true,
},
"profiles_sharing_access_level": null
}
```
### Luồng chia sẻ quyền truy cập giữa các người dùng \{#shared-access-between-users-flow\}
Tùy chọn này cho phép nhiều người dùng chia sẻ cùng mức độ truy cập nếu thiết bị của họ được đăng nhập vào cùng Apple/Google ID. Điều này hữu ích khi người dùng cài đặt lại ứng dụng và đăng nhập bằng email khác — họ vẫn sẽ có quyền truy cập vào giao dịch mua trước đó. Với tùy chọn này, nhiều người dùng được xác định có thể chia sẻ cùng mức độ truy cập. Trong khi mức độ truy cập được chia sẻ, tất cả các giao dịch được ghi lại dưới
Đây là phân tích chi tiết các trường liên quan đến gán mức độ truy cập và chia sẻ trong các sự kiện được tạo ra trong kịch bản này:
**User B: Access level updated (được gửi khi User B đăng nhập và quyền truy cập được cấp)**
```json showLineNumbers
{
"profile_id": "00000000-0000-0000-0000-000000000000",
"customer_user_id": UserA,
"event_properties": {
"profile_has_access_level": true,
},
"profiles_sharing_access_level": [
{
"profile_id": "00000000-0000-0000-0000-000000000001,
"customer_user_id": UserB
}
]
}
```
### Luồng không chia sẻ quyền truy cập giữa các người dùng \{#access-not-shared-between-users-flow\}
Với tùy chọn này, chỉ hồ sơ người dùng đầu tiên nhận được mức độ truy cập mới giữ nó vĩnh viễn. Điều này lý tưởng khi các giao dịch mua cần được gắn với một
---
# File: event-statuses
---
---
title: "Trạng thái sự kiện tích hợp"
description: ""
---
Adapty xác định khả năng gửi thành công dựa trên mã trạng thái HTTP, coi bất kỳ phản hồi nào ngoài phạm vi `200-399` là thất bại.
Bạn có thể theo dõi trạng thái của các sự kiện tích hợp trong **Event List** trên Adapty Dashboard. Hệ thống hiển thị trạng thái cho tất cả các tích hợp đã bật, bất kể loại sự kiện cụ thể có được bật cho tích hợp đó hay không.
- Đen: Sự kiện đã được gửi thành công.
- Xám: Loại sự kiện bị tắt cho tích hợp này.
- Đỏ: Có sự cố với tích hợp cần được xử lý.
Để biết thêm chi tiết về các sự kiện thất bại, hãy di chuột qua tên tích hợp để xem tooltip với thông tin lỗi cụ thể.
**Event Feed** chỉ hiển thị dữ liệu trong hai tuần gần nhất để tối ưu hóa hiệu suất. Giới hạn này giúp cải thiện tốc độ tải trang, giúp người dùng dễ dàng điều hướng và phân tích các sự kiện một cách hiệu quả hơn.
---
# File: adjust
---
---
title: "Adjust"
description: "Kết nối Adjust với Adapty để theo dõi gói đăng ký và phân tích tốt hơn."
---
[Adjust](https://www.adjust.com/) là một trong những nền tảng đo lường di động (MMP) hàng đầu, thu thập và trình bày dữ liệu từ các chiến dịch marketing, giúp các công ty theo dõi hiệu quả chiến dịch của mình.
Adapty cung cấp bộ dữ liệu đầy đủ để bạn theo dõi [sự kiện gói đăng ký](events) từ các cửa hàng ở một nơi duy nhất. Với Adapty, bạn có thể dễ dàng nắm bắt hành vi của người dùng, hiểu sở thích của họ, và sử dụng thông tin đó để giao tiếp một cách có mục tiêu và hiệu quả. Vì vậy, tích hợp này cho phép bạn theo dõi sự kiện gói đăng ký trong Adjust và phân tích chính xác doanh thu mà các chiến dịch của bạn tạo ra.
Tích hợp giữa Adapty và Adjust hoạt động theo hai hướng chính.
1. **Adapty nhận dữ liệu attribution từ Adjust**
Sau khi thiết lập tích hợp Adjust, Adapty sẽ bắt đầu nhận dữ liệu attribution từ Adjust. Bạn có thể dễ dàng truy cập và xem dữ liệu này trên trang hồ sơ người dùng.
2. **Adapty gửi sự kiện gói đăng ký đến Adjust**
Adapty có thể gửi tất cả sự kiện gói đăng ký đã được cấu hình trong tích hợp của bạn đến Adjust. Nhờ đó, bạn có thể theo dõi các sự kiện này trong Adjust dashboard. Tích hợp này rất hữu ích để đánh giá hiệu quả của các chiến dịch quảng cáo.
## Thiết lập tích hợp \{#set-up-integration\}
### Kết nối Adapty với Adjust \{#connect-adapty-to-adjust\}
1. Mở Adapty Dashboard và điều hướng đến [Integrations > Adjust](https://app.adapty.io/integrations/adjust).
2. Bật toggle ở đầu trang.
3. Điền vào các trường và thiết lập thông tin xác thực.
3. Nếu bạn đã bật xác thực OAuth trên nền tảng Adjust, bạn bắt buộc phải cung cấp **OAuth Token** trong quá trình tích hợp cho ứng dụng iOS và Android của mình.
4. Tiếp theo, cung cấp **app tokens** cho ứng dụng iOS và Android. Mở Adjust dashboard và bạn sẽ thấy danh sách ứng dụng của mình.
:::note
Bạn có thể có các ứng dụng Adjust khác nhau cho iOS và Android, vì vậy trong Adapty có hai mục riêng biệt cho chúng. Nếu bạn chỉ có một ứng dụng Adjust, hãy điền cùng một thông tin.
:::
5. Chọn ứng dụng từ danh sách và sao chép **App Token**. Dán token vào trường tương ứng trên Adapty dashboard.
### Cấu hình sự kiện và tags \{#configure-events-and-tags\}
Adjust hoạt động hơi khác so với các nền tảng khác. Bạn cần tạo sự kiện thủ công trong Adjust dashboard, lấy token sự kiện, rồi sao chép-dán chúng vào các sự kiện tương ứng trong Adapty.
Vì vậy, bước đầu tiên là tìm token sự kiện cho tất cả các sự kiện bạn muốn Adapty gửi. Để làm điều đó:
1. Trong Adjust dashboard, mở ứng dụng của bạn và chuyển sang tab **Events**.
1. Sao chép token sự kiện và dán vào Adapty. Phía dưới thông tin xác thực, có ba nhóm sự kiện bạn có thể gửi đến Adjust từ Adapty. Xem danh sách đầy đủ các sự kiện mà Adapty cung cấp [tại đây](events).
Adapty sẽ gửi sự kiện gói đăng ký đến Adjust thông qua tích hợp server-to-server, cho phép bạn xem tất cả sự kiện gói đăng ký trong Adjust dashboard và liên kết chúng với các chiến dịch thu hút người dùng.
:::important
Lưu ý những điều sau:
- Adjust không hỗ trợ sự kiện cũ hơn 58 ngày. Vì vậy, nếu bạn có sự kiện cũ hơn 58 ngày, Adapty vẫn sẽ gửi đến Adjust nhưng thời gian của sự kiện sẽ được thay thế bằng timestamp hiện tại.
- Adjust không hỗ trợ IPv6. Nếu bạn tắt tính năng thu thập IP trong SDK trong **App settings** hoặc khi kích hoạt SDK, chỉ có IPv6 phía backend được gửi đi và việc theo dõi có thể thất bại — hãy giữ tính năng thu thập IP của SDK được bật để đảm bảo sử dụng IPv4.
:::
### Kết nối ứng dụng với Adjust \{#connect-your-app-to-adjust\}
Sau khi hoàn thành các bước trên, hãy thêm hai phương thức sau vào ứng dụng. Chúng sẽ thiết lập kết nối giữa ứng dụng và Adjust:
1. **Để gửi dữ liệu gói đăng ký đến Adjust**: Truyền Adjust device ID vào phương thức SDK `setIntegrationIdentifier()`
2. **Để nhận dữ liệu attribution từ Adjust**: Cập nhật dữ liệu attribution bằng phương thức SDK `updateAttribution()`
Đối với Adjust phiên bản 5.0 trở lên, hãy sử dụng ví dụ sau:
Cả hai thông tin đều có thể tìm thấy trong Airbridge dashboard của bạn tại mục [Third-party Integrations > Adapty](https://app.airbridge.io/app/testad/integrations/third-party/adapty).
Trường Adapty API token được tạo sẵn từ phía backend của Adapty. Bạn cần sao chép giá trị Adapty API token và dán vào Airbridge Dashboard trong trường Adapty Authorization Token.
### Cấu hình sự kiện và tags \{#configure-events-and-tags\}
Bên dưới phần thông tin xác thực là ba nhóm sự kiện bạn có thể gửi từ Adapty đến Airbridge.
Chỉ cần bật những sự kiện bạn cần.
### Kết nối ứng dụng với Airbridge \{#connect-your-app-to-airbridge\}
Để tích hợp, bạn cần truyền `airbridge_device_id` vào profile builder và gọi `setIntegrationIdentifier` như ví dụ dưới đây:
## Thiết lập tích hợp \{#set-up-integration\}
### Kết nối Adapty với AdServices framework \{#connect-adapty-to-the-adservices-framework\}
Apple Ads qua [AdServices](https://developer.apple.com/documentation/adservices) yêu cầu một số cấu hình trong Adapty Dashboard, đồng thời bạn cũng cần bật tính năng này phía ứng dụng. Để thiết lập Apple Ads bằng AdServices framework qua Adapty, làm theo các bước sau:
#### Bước 1: Lấy public key \{#step-1-obtain-public-key\}
Trong Adapty Dashboard, truy cập [Settings -> Apple Ads.](https://app.adapty.io/settings/apple-search-ads)
Tìm public key đã được tạo sẵn (Adapty cung cấp cặp khóa cho bạn) và sao chép nó.
:::note
Nếu bạn đang dùng dịch vụ khác hoặc giải pháp riêng cho attribution Apple Ads, bạn có thể tải lên private key của mình.
:::
#### Bước 2: Cấu hình quản lý người dùng trên Apple Ads \{#step-2-configure-user-management-on-apple-ads\}
Trong [tài khoản Apple Ads](https://ads.apple.com/app-store) của bạn, vào trang **Settings > User Management**. Để Adapty có thể lấy dữ liệu attribution, bạn cần mời một tài khoản Apple ID khác và cấp quyền truy cập API Account Manager. Bạn có thể dùng bất kỳ tài khoản nào bạn có quyền truy cập hoặc tạo một tài khoản mới riêng cho mục đích này. Điều quan trọng là bạn phải có thể đăng nhập vào Apple Ads bằng Apple ID đó.
#### Bước 3: Tạo thông tin xác thực API \{#step-3-generate-api-credentials\}
Tiếp theo, đăng nhập vào tài khoản vừa thêm trong Apple Ads. Vào Settings -> API trong giao diện Apple Ads. Dán public key đã sao chép vào trường được chỉ định. Tạo thông tin xác thực API mới.
#### Bước 4: Cấu hình Adapty với thông tin xác thực Apple Ads \{#step-4-configure-adapty-with-apple-ads-credentials\}
Sao chép các trường Client ID, Team ID và Key ID từ cài đặt Apple Ads. Trong Adapty Dashboard, dán các thông tin này vào các trường tương ứng.
### Kết nối ứng dụng với mạng AdServices \{#connect-your-app-to-the-adservices-network\}
Sau khi hoàn tất [thiết lập AdServices framework](#connect-the-adservices-framework), Adapty sẽ tự động bắt đầu thu thập dữ liệu attribution Apple Search Ad. Bạn không cần thêm bất kỳ đoạn code SDK nào.
Đối với ứng dụng iOS, dữ liệu attribution này sẽ **luôn** được ưu tiên hơn dữ liệu từ các nguồn khác. Nếu không muốn hành vi này, hãy *tắt* attribution ASA theo hướng dẫn bên dưới.
## Tắt tích hợp \{#disable-integration\}
Để tắt attribution Apple Search Ads, mở tab [**App Settings** -> **Apple Search Ads**](https://app.adapty.io/settings/apple-search-ads) và tắt công tắc **Receive Apple Search Ads attribution**.
:::warning
Lưu ý rằng việc tắt tính năng này sẽ hoàn toàn dừng việc nhận analytics ASA. Kết quả là, ASA sẽ không còn được sử dụng trong analytics hoặc gửi đến các tích hợp. Ngoài ra, SplitMetrics Acquire và Asapty sẽ ngừng hoạt động vì chúng phụ thuộc vào attribution ASA để hoạt động đúng.
Dữ liệu attribution nhận được trước thay đổi này sẽ không bị ảnh hưởng.
:::
## Tải lên key của riêng bạn \{#uploading-your-own-keys\}
:::note
Tùy chọn
Các bước này không bắt buộc cho attribution Apple Ads, chỉ cần thiết khi làm việc với các dịch vụ khác như Asapty hoặc giải pháp của riêng bạn.
:::
Bạn có thể dùng cặp khóa public-private của riêng mình nếu đang sử dụng dịch vụ khác hoặc giải pháp riêng cho attribution ASA.
### Bước 1 \{#step-1\}
Tạo private key trong Terminal
```text showLineNumbers title="Text"
openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem
```
Tải lên trong Adapty Settings -> Apple Ads (nút Upload private key)
### Bước 2 \{#step-2\}
Tạo public key trong Terminal
```text showLineNumbers title="Text"
openssl ec -in private-key.pem -pubout -out public-key.pem
```
Bạn có thể dùng public key này trong cài đặt Apple Ads của tài khoản có vai trò API Account Manager. Vì vậy, bạn có thể sử dụng các giá trị Client ID, Team ID và Key ID đã tạo cho Adapty và các dịch vụ khác.
---
# File: switch-from-appsflyer-s2s-api-2-to-3
---
---
title: "Chuyển từ AppsFlyer S2S API 2 sang 3"
description: "Nâng cấp từ AppsFlyer S2S API 2 lên API 3 trong Adapty."
---
Theo [thông báo chính thức từ AppsFlyer](https://support.appsflyer.com/hc/en-us/articles/20509378973457-Bulletin-Upgrading-the-AppsFlyer-S2S-API), để tăng cường bảo mật cho việc sử dụng API và giảm thiểu gian lận, AppsFlyer đã nâng cấp API server-to-server (S2S) dành cho các sự kiện trong ứng dụng. Endpoint hiện tại sẽ bị ngừng hỗ trợ trong tương lai, vì vậy chúng tôi khuyến nghị bạn bắt đầu lên kế hoạch chuyển đổi.
Adapty hỗ trợ AppsFlyer S2S API 3 và cung cấp cho bạn trải nghiệm chuyển đổi liền mạch từ API 2. Lưu ý rằng quá trình chuyển đổi này là một chiều — bạn sẽ không thể quay lại API 2 sau khi đã thực hiện thay đổi.
Để chuyển từ AppsFlyer S2S API 2 sang 3:
1. Truy cập [trang AppsFlyer](https://www.appsflyer.com/home) và đăng nhập.
2. Nhấp vào **Tên tài khoản của bạn** -> **Security Center** ở góc trên bên trái của dashboard.
3. Trong cửa sổ **Manage your account security**, nhấp vào nút **Manage your AppsFlyer API and S2S tokens**.
4. Nếu bạn chưa có S2S token, nhấp vào nút **New token**. Nếu đã có, hãy chuyển sang bước 8.
5. Trong cửa sổ **New token**, nhập tên cho token. Tên này chỉ dùng để bạn tham khảo.
6. Chọn **S2S** trong danh sách **Choose type**.
7. Đừng quên nhấp vào nút **Create new token** để lưu token mới.
8. Trong cửa sổ **Tokens**, sao chép S2S token.
9. Mở [**Integrations** -> **AppsFlyer**](https://app.adapty.io/integrations/appsflyer) trong Adapty Dashboard.
10. Trong trường **AppsFlyer S2S API**, chọn **API 3**.
11. Dán S2S key vừa sao chép vào các trường **Dev key for iOS** và **Dev key for Android**.
12. Nhấp vào nút **Save** để xác nhận việc chuyển đổi.
Ngay lúc này, tích hợp của bạn sẽ chuyển ngay sang AppsFlyer S2S API 3 và các sự kiện mới sẽ được gửi đến URL mới: `https://api3.appsflyer.com/inappevent`.
---
# File: asapty
---
---
title: "Asapty"
description: "Khám phá Asapty và vai trò của nó trong hệ sinh thái gói đăng ký của Adapty."
---
Sử dụng tích hợp [Asapty](https://asapty.com/), bạn có thể tối ưu hóa các chiến dịch Search Ads. Adapty gửi các sự kiện gói đăng ký tới Asapty, giúp bạn xây dựng các dashboard tùy chỉnh dựa trên attribution từ Apple Search Ads.
Tích hợp này không bổ sung dữ liệu attribution vào Adapty, vì chúng tôi đã có đầy đủ thông tin cần thiết trực tiếp từ [ASA](apple-search-ads).
## Thiết lập tích hợp \{#set-up-integration\}
### Kết nối Adapty với Asapty \{#connect-adapty-to-asapty\}
Để tích hợp Asapty, hãy vào [Integrations > Asapty](https://app.adapty.io/integrations/asapty) trong Adapty dashboard và điền giá trị cho trường Asapty ID.
Asapty ID có thể tìm thấy trong mục Settings > General trong tài khoản Asapty của bạn.
### Cấu hình sự kiện và thẻ \{#configure-events-and-tags\}
Bên dưới phần thông tin xác thực, có ba nhóm sự kiện bạn có thể gửi tới Asapty từ Adapty. Chỉ cần bật những sự kiện bạn cần. Xem danh sách đầy đủ các sự kiện mà Adapty cung cấp [tại đây](events).
Chúng tôi khuyến nghị sử dụng tên sự kiện mặc định do Asapty cung cấp. Tuy nhiên, bạn có thể tùy chỉnh tên sự kiện theo nhu cầu của mình.
### Kết nối ứng dụng của bạn với Asapty \{#connect-your-app-to-asapty\}
Sau khi hoàn thành các bước trên, Adapty sẽ tự động nhận dữ liệu attribution từ Asapty. Bạn không cần phải yêu cầu dữ liệu attribution một cách tường minh trong code ứng dụng. Để đảm bảo độ chính xác của dữ liệu attribution, hãy cấu hình Asapty để chia sẻ `customerUserId` cùng với dữ liệu của mỗi sự kiện.
## Cấu trúc sự kiện Asapty \{#asapty-event-structure\}
Adapty gửi các sự kiện tới Asapty qua GET request sử dụng query parameter. Mỗi URL sự kiện có dạng như sau:
```
https://asapty.com/_api/mmpEvents/?source=adapty&asaptyid=a1b2c3d4&keywordid=12345&adgroupid=67890&campaignid=11223&conversiondate=1709294400000&event_name=subscription_renewed&install_time=1709100000&app_name=MyApp&json=%7B%22af_revenue%22%3A%229.99%22%2C%22af_currency%22%3A%22USD%22...%7D
```
Các query parameter:
| Parameter | Type | Description |
|:-----------------|:-------|:-----------------------------------------------------|
| `source` | String | Luôn là "adapty". |
| `asaptyid` | String | Asapty ID từ thông tin xác thực của bạn. |
| `keywordid` | String | Apple Search Ads Keyword ID (nếu có). |
| `adgroupid` | String | Apple Search Ads Ad Group ID (nếu có). |
| `campaignid` | String | Apple Search Ads Campaign ID (nếu có). |
| `conversiondate` | Long | Timestamp của sự kiện tính bằng **mili giây**. |
| `event_name` | String | Tên sự kiện (được ánh xạ từ sự kiện Adapty). |
| `install_time` | Long | Timestamp của lần cài đặt tính bằng giây. |
| `app_name` | String | Tiêu đề ứng dụng từ Adapty (nếu có). |
| `json` | String | Chuỗi JSON đã được URL-encode chứa chi tiết sự kiện (xem bên dưới). |
Tham số `json` là một chuỗi JSON đã được URL-encode chứa các trường sau:
| Parameter | Type | Description |
|:--------------------------|:-------|:---------------------------------------------|
| `af_revenue` | String | Số tiền doanh thu dưới dạng chuỗi. |
| `af_currency` | String | Mã tiền tệ (ví dụ: "USD"). |
| `transaction_id` | String | Transaction ID từ cửa hàng. |
| `original_transaction_id` | String | Transaction ID gốc từ cửa hàng. |
| `purchase_date` | Long | Timestamp mua hàng tính bằng mili giây. |
| `original_purchase_date` | Long | Timestamp mua hàng gốc tính bằng mili giây. |
| `environment` | String | `Production` hoặc `Sandbox`. |
| `vendor_product_id` | String | Product ID từ cửa hàng. |
| `profile_country` | String | Mã quốc gia dựa theo IP của người dùng. |
| `store_country` | String | Mã quốc gia của cửa hàng người dùng. |
## Xử lý sự cố \{#troubleshooting\}
- Đảm bảo bạn đã cấu hình [Apple Search Ads](apple-search-ads) trong Adapty và [tải lên thông tin xác thực](https://app.adapty.io/settings/apple-search-ads); nếu không có, Asapty sẽ không hoạt động.
- Chỉ những hồ sơ người dùng có dữ liệu attribution ASA chi tiết (không phải organic) mới gửi sự kiện tới Asapty. Bạn sẽ thấy thông báo "The user profile is missing the required integration data." nếu dữ liệu attribution không đầy đủ.
- Các hồ sơ người dùng được tạo trước khi cấu hình tích hợp sẽ không thể gửi sự kiện tới Asapty.
- Nếu tích hợp với Adapty không hoạt động dù đã thiết lập đúng, hãy kiểm tra xem toggle **Receive Apple Search Ads attribution in Adapty** đã được bật trong tab [**App Settings** -> **Apple Search Ads**](https://app.adapty.io/settings/apple-search-ads) chưa.
---
# File: branch
---
---
title: "Branch"
description: "Tích hợp Branch với Adapty để theo dõi deep link và lượt chuyển đổi trong ứng dụng."
---
[Branch](https://www.branch.io/) giúp doanh nghiệp tiếp cận, tương tác và đánh giá kết quả trên nhiều thiết bị, kênh và nền tảng khác nhau. Đây là nền tảng thân thiện với người dùng, được thiết kế để tăng doanh thu di động thông qua các liên kết chuyên biệt hoạt động liền mạch trên mọi thiết bị, kênh và nền tảng.
Adapty cung cấp bộ dữ liệu đầy đủ giúp bạn theo dõi [các sự kiện gói đăng ký](events) từ các cửa hàng tại một nơi. Với Adapty, bạn có thể dễ dàng xem hành vi của người dùng, tìm hiểu sở thích của họ và sử dụng thông tin đó để giao tiếp theo cách có mục tiêu và hiệu quả.
Tích hợp giữa Adapty và Branch hoạt động theo hai cách chính.
1. **Nhận dữ liệu attribution từ Branch**
Sau khi thiết lập tích hợp Branch, Adapty sẽ bắt đầu nhận dữ liệu attribution từ Branch. Bạn có thể dễ dàng xem dữ liệu này trên trang hồ sơ người dùng.
2. **Gửi sự kiện gói đăng ký đến Branch**
Adapty có thể gửi tất cả các sự kiện gói đăng ký được cấu hình trong phần tích hợp của bạn đến Branch. Nhờ đó, bạn có thể theo dõi các sự kiện này trong Branch dashboard.
## Thiết lập tích hợp \{#set-up-integration\}
### Kết nối Adapty với Branch \{#connect-adapty-to-branch\}
Để tích hợp Branch, vào [Integrations > Branch](https://app.adapty.io/integrations/branch) trong Adapty Dashboard, bật toggle từ tắt sang bật và điền thông tin vào các trường.
Để lấy giá trị cho trường **Branch Key**, mở [Account Settings](https://dashboard.branch.io/account-settings/profile) trên Branch và tìm trường **Branch Key**. Sử dụng giá trị này cho trường **Key test** (cho Sandbox) hoặc **Key live** (cho Production) trong Adapty Dashboard. Trong Branch, chuyển đổi giữa môi trường Live và Test để lấy key tương ứng.
### Cấu hình sự kiện và tag \{#configure-events-and-tags\}
Phía dưới phần thông tin xác thực, có ba nhóm sự kiện bạn có thể gửi đến Branch từ Adapty. Chỉ cần bật những sự kiện bạn cần. Xem danh sách đầy đủ các sự kiện mà Adapty cung cấp [tại đây](events).
Bạn có thể gửi sự kiện kèm theo Proceeds \(sau khi Apple/Google khấu trừ\) hoặc chỉ doanh thu. Ngoài ra, bạn có thể chọn tùy chọn báo cáo theo tiền tệ của người dùng.
Chúng tôi khuyên bạn nên dùng tên sự kiện mặc định do Adapty cung cấp. Tuy nhiên, bạn có thể thay đổi tên sự kiện tùy theo nhu cầu.
Adapty sẽ gửi các sự kiện gói đăng ký đến Branch thông qua tích hợp server-to-server, cho phép bạn xem tất cả sự kiện gói đăng ký trong Branch dashboard và liên kết chúng với các chiến dịch acquisition của bạn.
### Kết nối ứng dụng với Branch \{#connect-your-app-to-branch\}
1. Gọi phương thức SDK `.setIntegrationIdentifier()` để khởi tạo kết nối. Bạn có thể truyền Branch Identity ID vào tham số `customerUserId`.
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
1. Để tìm App ID, mở trang ứng dụng của bạn trong [App Store Connect](https://appstoreconnect.apple.com/), vào trang **App Information** trong mục **General**, và tìm **Apple ID** ở góc dưới bên trái màn hình.
2. Bạn cần có ứng dụng trên nền tảng [Meta for Developers](https://developers.facebook.com/). Đăng nhập vào ứng dụng của bạn rồi vào phần cài đặt nâng cao. Bạn có thể tìm thấy **App ID** ở phần tiêu đề.
3. Tắt tính năng theo dõi phía client trong cấu hình Meta SDK để tránh tính doanh thu hai lần trong Meta Ads Manager. Bạn có thể tìm thấy cài đặt này trong Meta Developer Console tại **App Settings > Advanced Settings**. Đặt **Log in-app events automatically** thành "No". Điều này đảm bảo rằng sự kiện doanh thu chỉ được theo dõi thông qua tích hợp của Adapty.
Để theo dõi sự kiện cài đặt và sử dụng, bạn cần kích hoạt Meta SDK trong mã nguồn của mình. Bạn có thể tìm thấy hướng dẫn triển khai trong tài liệu Meta SDK cho nền tảng của bạn:
- [iOS SDK](https://developers.facebook.com/docs/ios/getting-started)
- [Android SDK](https://developers.facebook.com/docs/android/getting-started)
- [Unity SDK](https://developers.facebook.com/docs/unity/getting-started/canvas)
Bạn cũng có thể sử dụng tích hợp này với ứng dụng Android. Nếu bạn đã thiết lập cấu hình Android SDK trong **App Settings**, chỉ cần thiết lập **Facebook App ID** là đủ.
### Cấu hình sự kiện và thẻ \{#configure-events-and-tags\}
Lưu ý rằng tích hợp Facebook Ads được thiết kế đặc biệt cho các công ty sử dụng Meta cho chiến dịch quảng cáo và tối ưu hóa dựa trên hành vi khách hàng. Tích hợp này hỗ trợ các sự kiện chuẩn của Meta cho mục đích tối ưu hóa. Do đó, không thể chỉnh sửa tên sự kiện trong tích hợp Meta Ads. Adapty ánh xạ các sự kiện khách hàng của bạn sang các sự kiện Meta tương ứng để phân tích chính xác.
| Sự kiện Adapty | Sự kiện Meta Ads |
| :---------------------------- | :-------------------------- |
| Subscription initial purchase | Subscribe |
| Subscription renewed | Subscribe |
| Subscription cancelled | CancelSubscription |
| Trial started | StartTrial |
| Trial converted | Subscribe |
| Trial cancelled | CancelTrial |
| Non subscription purchase | fb_mobile_purchase |
| Billing issue detected | billing_issue_detected |
| Entered grace period | entered_grace_period |
| Auto renew off | auto_renew_off |
| Auto renew on | auto_renew_on |
| Auto renew off subscription | auto_renew_off_subscription |
| Auto renew on subscription | auto_renew_on_subscription |
StartTrial, Subscribe, CancelSubscription là các sự kiện chuẩn.
Để bật các sự kiện cụ thể, chỉ cần bật những sự kiện bạn cần. Nếu nhiều tên sự kiện được chọn, Adapty sẽ hợp nhất dữ liệu từ tất cả các sự kiện đã chọn thành một tên sự kiện Adapty duy nhất.
### Kết nối ứng dụng của bạn với Facebook Ads \{#connect-your-app-to-facebook-ads\}
Nếu bạn thực hiện các bước trên, Facebook sẽ tự động nhận dữ liệu gói đăng ký từ Adapty.
Theo những thay đổi về IDFA trong iOS 14.5, chúng tôi khuyến nghị bạn yêu cầu `facebookAnonymousId` của người dùng từ Facebook. Như vậy, nếu IDFA của người dùng không khả dụng, tích hợp vẫn sẽ tiếp tục hoạt động. Hãy làm theo
Bên dưới phần thông tin xác thực, có ba nhóm sự kiện bạn có thể gửi đến Singular từ Adapty. Xem danh sách đầy đủ các sự kiện mà Adapty cung cấp [tại đây](events).
Chúng tôi khuyến nghị sử dụng tên sự kiện mặc định do Adapty cung cấp. Tuy nhiên, bạn có thể thay đổi tên sự kiện theo nhu cầu của mình.
Adapty sẽ gửi các sự kiện gói đăng ký đến Singular thông qua tích hợp server-to-server, cho phép bạn xem tất cả các sự kiện gói đăng ký trong dashboard Singular và liên kết chúng với các chiến dịch quảng cáo của bạn.
:::warning
Các hồ sơ người dùng được tạo trước khi cấu hình tích hợp sẽ không thể gửi sự kiện đến Singular.
:::
### Kết nối ứng dụng của bạn với Singular \{#connect-your-app-to-singular\}
Tích hợp giữa Adapty và Singular là server-to-server. Do đó, bạn không cần thêm bất kỳ đoạn code nào vào ứng dụng của mình.
## Cấu trúc sự kiện \{#event-structure\}
Adapty gửi sự kiện đến Singular thông qua GET request sử dụng query parameters. Mỗi sự kiện có cấu trúc như sau:
```json
{
"n": "subscription_renewed",
"a": "singular_sdk_key_123",
"p": "iOS",
"i": "com.example.app",
"ip": "192.168.100.1",
"idfa": "00000000-0000-0000-0000-000000000000",
"idfv": "00000000-0000-0000-0000-000000000000",
"ve": "17.0.1",
"att_authorization_status": 3,
"custom_user_id": "user_12345",
"utime": 1709294400,
"amt": 9.99,
"cur": "USD",
"purchase_product_id": "yearly.premium.6999",
"purchase_transaction_id": "GPA.3383...",
"e": "{\"is_revenue_event\":true,\"amt\":9.99,\"cur\":\"USD\",\"purchase_product_id\":\"yearly.premium.6999\",\"purchase_transaction_id\":\"GPA.3383...\"}"
}
```
Trong đó:
| Tham số | Kiểu | Mô tả |
|:---------------------------|:--------|:---------------------------------------------------------------|
| `n` | String | Tên sự kiện (được ánh xạ từ sự kiện Adapty). |
| `a` | String | Singular SDK Key của bạn. |
| `p` | String | Nền tảng ("iOS" hoặc "Android"). |
| `i` | String | Store App ID (Bundle ID). |
| `ip` | String | Địa chỉ IP của người dùng. |
| `idfa` | String | **Chỉ iOS**. ID for Advertisers (chữ hoa). |
| `idfv` | String | **Chỉ iOS**. ID for Vendors (chữ hoa). |
| `aifa` | String | **Chỉ Android**. Google Advertising ID (chữ thường). |
| `andi` | String | **Chỉ Android**. Android ID (chữ thường). |
| `asid` | String | **Chỉ Android**. App Set ID (chữ thường). |
| `ve` | String | Phiên bản hệ điều hành. |
| `att_authorization_status` | Integer | **Chỉ iOS**. Trạng thái ATT (ví dụ: `3` là đã được phép). |
| `custom_user_id` | String | Customer User ID của người dùng. |
| `utime` | Long | Timestamp UNIX của sự kiện tính bằng giây. |
| `amt` | Float | Số tiền doanh thu. |
| `cur` | String | Mã tiền tệ (ví dụ: "USD"). |
| `purchase_product_id` | String | Product ID từ cửa hàng. |
| `purchase_transaction_id` | String | Transaction ID gốc. |
| `e` | String | Chuỗi JSON chứa thông tin chi tiết sự kiện (xem bên dưới). |
Tham số `e` (dữ liệu sự kiện tùy chỉnh) là một chuỗi được mã hóa JSON chứa:
| Tham số | Kiểu | Mô tả |
|:--------------------------|:--------|:--------------------------------------------|
| `is_revenue_event` | Boolean | `true` nếu sự kiện có chứa doanh thu. |
| `amt` | Float | Số tiền doanh thu. |
| `cur` | String | Mã tiền tệ. |
| `purchase_product_id` | String | Product ID từ cửa hàng. |
| `purchase_transaction_id` | String | Transaction ID gốc. |
---
# File: tenjin
---
---
title: "Tenjin integration"
description: ""
---
Tenjin là nền tảng attribution và phân tích dành cho nhà phát triển ứng dụng và marketer. Nền tảng này cung cấp các công cụ để đo lường và tối ưu hóa chiến dịch thu hút người dùng thông qua các thông tin chi tiết về hiệu suất ứng dụng và hành vi người dùng. Với cách tiếp cận minh bạch và linh hoạt, Tenjin tổng hợp dữ liệu từ các mạng quảng cáo và cửa hàng ứng dụng, giúp các nhóm phân tích ROI, theo dõi chuyển đổi và giám sát các chỉ số hiệu suất quan trọng.
Bằng cách chuyển tiếp [các sự kiện gói đăng ký](events) tới Tenjin, bạn có thể thấy chính xác nguồn gốc của từng chuyển đổi và chiến dịch nào mang lại giá trị cao nhất trên tất cả các kênh, nền tảng và thiết bị. Về cơ bản, dashboard Tenjin cung cấp phân tích nâng cao cho các chiến dịch marketing.
Bằng cách chuyển tiếp attribution từ Tenjin sang Adapty, bạn làm giàu thêm dữ liệu phân tích của Adapty với các tiêu chí lọc bổ sung để sử dụng trong phân tích cohort và chuyển đổi.
Tích hợp này hoạt động theo hai hướng chính:
1. **Nhận dữ liệu attribution từ Tenjin**
Sau khi tích hợp, Adapty thu thập dữ liệu attribution từ Tenjin. Bạn có thể xem thông tin này trên trang hồ sơ người dùng trong Adapty Dashboard.
2. **Gửi sự kiện gói đăng ký đến Tenjin**
Adapty gửi các sự kiện mua hàng đến Tenjin theo thời gian thực. Các sự kiện này giúp đánh giá hiệu quả của các chiến dịch quảng cáo trực tiếp trong dashboard của Tenjin.
| Đặc điểm tích hợp | Mô tả |
| ------------------ | ------------------------------------------------------------ |
| Lịch trình | Thời gian thực |
| Chiều truyền dữ liệu | Truyền hai chiều:
3. Đăng nhập vào [Tenjin Dashboard](https://tenjin.com/).
4. Vào **Configuration** -> **Apps** trong menu điều hướng.
5. Chọn ứng dụng cho nền tảng của bạn (iOS hoặc Android) và chuyển đến tab **App and SDK**.
6. Trong tab **App and SDK**, nhấn **Copy** ở cột **SDK Key**. Nếu bạn chưa có SDK key, nhấn nút **Generate SDK Key** để tạo một cái.
7. Quay lại Adapty Dashboard và dán SDK Key vừa sao chép vào trường tương ứng:
- Với ứng dụng iOS: dán vào trường **iOS SDK Key** hoặc **iOS Sandbox SDK Key**
- Với ứng dụng Android: dán vào trường **Android SDK Key** hoặc **Android Sandbox SDK Key**
:::info
Tenjin không có chế độ Sandbox riêng cho tích hợp server-to-server. Hãy dùng một ứng dụng Tenjin riêng biệt hoặc dùng cùng một key cho cả sự kiện production lẫn sandbox.
:::
8. Nếu bạn có ứng dụng trên cả hai nền tảng, lặp lại bước 5-7 cho nền tảng còn lại.
9. (Tùy chọn) Điều chỉnh phần **How the revenue data should be sent** nếu cần. Để biết giải thích chi tiết về các cài đặt, tham khảo [Integration settings](configuration#integration-settings).
10. Nhấn **Save** để hoàn tất thiết lập.
Adapty sẽ bắt đầu gửi sự kiện mua hàng đến Tenjin và nhận dữ liệu attribution. Bạn có thể điều chỉnh việc chia sẻ sự kiện trong phần **Events names**.
### Cấu hình sự kiện và tag \{#configure-events-and-tags\}
Tenjin chỉ chấp nhận các sự kiện mua hàng và **Trial started**. Trong phần **Events names**, chọn những sự kiện cần chia sẻ với Tenjin để phù hợp với mục tiêu theo dõi của bạn.
### Kết nối ứng dụng với Tenjin \{#connect-your-app-to-tenjin\}
Dùng phương thức SDK `Adapty.updateAttribution()` để lấy dữ liệu attribution từ Tenjin và truyền nó sang Adapty.
2. Bật **Amplitude integration** để kích hoạt.
3. Điền vào các trường tích hợp:
| Trường | Mô tả |
| ------------------------------------------ | ------------------------------------------------------------ |
| **Amplitude iOS/ Android/ Stripe API key** | Nhập **API Key** của Amplitude cho iOS/ Android/ Stripe vào Adapty. Tìm thấy nó trong **Project settings** trên Amplitude. Để được hỗ trợ, xem [tài liệu Amplitude](https://amplitude.com/docs/apis/authentication). Bắt đầu với các key **Sandbox** để thử nghiệm, sau đó chuyển sang key **Production** sau khi thử nghiệm thành công. |
4. Các cài đặt tùy chọn để tùy chỉnh thêm:
| Tham số | Mô tả |
| --------------------------------------- | ------------------------------------------------------------ |
| **How the revenue data should be sent** | Chọn gửi doanh thu gộp hay doanh thu sau thuế và hoa hồng. Xem [Hoa hồng cửa hàng và thuế](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue) để biết thêm chi tiết. |
| **Exclude historical events** | Chọn để loại trừ các sự kiện trước khi cài đặt Adapty SDK, tránh dữ liệu bị trùng lặp. Ví dụ: nếu người dùng đăng ký vào ngày 10 tháng 1 nhưng cài đặt Adapty SDK vào ngày 6 tháng 3, Adapty chỉ gửi các sự kiện từ ngày 6 tháng 3 trở đi. |
| **Send User Attributes** | Chọn tùy chọn này để gửi các thuộc tính người dùng như tùy chọn ngôn ngữ. |
| **Always populate user_id** | Adapty tự động gửi `device_id` dưới dạng `amplitudeDeviceId`. Đối với `user_id`, cài đặt này xác định hành vi:
Chúng tôi khuyến nghị sử dụng tên sự kiện mặc định do Adapty cung cấp. Tuy nhiên, bạn có thể thay đổi tên sự kiện theo nhu cầu. Adapty sẽ gửi các sự kiện gói đăng ký đến Amplitude thông qua tích hợp server-to-server, cho phép bạn xem tất cả sự kiện gói đăng ký trong dashboard Amplitude của mình.
### Cấu hình SDK \{#sdk-configuration\}
Sử dụng phương thức `setIntegrationIdentifier()` để thiết lập tham số `amplitude_device_id`. Đây là bước bắt buộc để thiết lập tích hợp.
Nếu bạn có đăng ký người dùng, bạn cũng có thể truyền `amplitude_user_id`.
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
4. Vào [Integrations > AppMetrica](https://app.adapty.io/integrations/appmetrica) trong Adapty Dashboard
5. Dán thông tin xác thực AppMetrica của bạn vào.
### Sự kiện và thẻ \{#events-and-tags\}
Adapty cho phép bạn gửi ba nhóm sự kiện tới AppMetrica. Bạn có thể bật các sự kiện cần theo dõi hiệu suất ứng dụng. Xem danh sách đầy đủ các sự kiện có sẵn trong [tài liệu về sự kiện](events).
:::note
AppMetrica đồng bộ sự kiện mỗi 4 giờ, vì vậy có thể có độ trễ trước khi sự kiện xuất hiện trên dashboard của bạn.
:::
:::tip
Chúng tôi khuyến nghị sử dụng tên sự kiện mặc định của Adapty để đảm bảo tính nhất quán, nhưng bạn có thể tùy chỉnh chúng để phù hợp với thiết lập phân tích hiện có của mình.
:::
### Cài đặt doanh thu \{#revenue-settings\}
Mặc định, Adapty gửi dữ liệu doanh thu dưới dạng thuộc tính trong các sự kiện, hiển thị trong báo cáo Events của AppMetrica. Bạn có thể cấu hình cách tính toán và hiển thị dữ liệu doanh thu này:
- **Tính toán doanh thu**: Chọn cách tính giá trị doanh thu để phù hợp với nhu cầu báo cáo tài chính của bạn:
- **Doanh thu gộp**: Hiển thị tổng doanh thu trước khi trừ bất kỳ khoản nào, hữu ích để theo dõi tổng số tiền khách hàng thanh toán
- **Doanh thu sau khi trừ hoa hồng cửa hàng**: Hiển thị doanh thu sau khi đã trừ phí App Store/Play Store, giúp bạn theo dõi thu nhập thực tế
- **Doanh thu sau khi trừ hoa hồng cửa hàng và thuế**: Hiển thị doanh thu ròng sau khi trừ cả phí cửa hàng và thuế áp dụng, cho bức tranh chính xác nhất về thu nhập của bạn
- **Báo cáo theo tiền tệ của người dùng**: Khi bật, doanh số được báo cáo theo tiền tệ địa phương của người dùng, giúp phân tích doanh thu theo khu vực dễ dàng hơn. Khi tắt, tất cả doanh số được quy đổi sang USD để báo cáo nhất quán trên các thị trường khác nhau.
- **Gửi sự kiện doanh thu**: Bật tùy chọn này để dữ liệu doanh thu xuất hiện không chỉ trong báo cáo Events mà còn trong báo cáo [In-app and ad revenue](https://appmetrica.yandex.com/docs/en/mobile-reports/revenue-report) của AppMetrica. Đảm bảo bạn không gửi doanh thu từ bất kỳ nguồn nào khác, vì điều này có thể dẫn đến trùng lặp dữ liệu.
- **Loại trừ sự kiện lịch sử**: Khi bật, Adapty sẽ không gửi các sự kiện xảy ra trước khi người dùng cài đặt ứng dụng có tích hợp Adapty SDK. Điều này giúp tránh trùng lặp dữ liệu nếu bạn đã gửi sự kiện tới analytics trước khi tích hợp Adapty.
### Cấu hình SDK \{#sdk-configuration\}
Để bật tích hợp AppMetrica trong ứng dụng, bạn cần thiết lập hai mã định danh:
1. `appmetrica_device_id`: Bắt buộc để tích hợp cơ bản
2. `appmetrica_profile_id`: Tùy chọn, nhưng được khuyến nghị nếu ứng dụng có chức năng đăng ký tài khoản người dùng
Sử dụng phương thức `setIntegrationIdentifier()` để thiết lập các giá trị này. Cách triển khai cho từng nền tảng:
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
### 2\. Tích hợp với Adapty \{#2-integrate-with-adapty\}
Adapty cần Firebase App ID và Google Analytics API Secret của bạn để gửi sự kiện và thuộc tính người dùng. Bạn có thể tìm các thông số này lần lượt trong Firebase Console và tab Google Analytics Data Streams.
Tiếp theo, truy cập trang chi tiết App's Stream trong phần Data Streams của cài đặt Admin trên [Google Analytics.](https://analytics.google.com/analytics/web/#/)
Trong **Additional settings**, chuyển đến trang **Measurement Protocol API secrets** và tạo một **API Secret** mới nếu chưa có. Sao chép giá trị đó.
Sau đó, bước tiếp theo của bạn là điều chỉnh tích hợp trong Adapty Dashboard. Bạn cần cung cấp cho chúng tôi Firebase App ID và Google Analytics API Secret cho nền tảng iOS, Android và/hoặc Stripe của mình.
:::note
Nếu bạn đang sử dụng tích hợp Stripe, hãy xem xét các giới hạn của nó trong [hướng dẫn](stripe#current-limitations) chuyên dụng. Những giới hạn này cũng sẽ áp dụng cho tích hợp Firebase.
:::
## Cấu hình SDK \{#sdk-configuration\}
:::important
Để tích hợp hoạt động, hãy đảm bảo bạn thêm Firebase vào ứng dụng trước:
- [iOS](https://firebase.google.com/docs/ios/setup)
- [Android](https://firebase.google.com/docs/android/setup)
- [React Native](https://firebase.google.com/docs/web/setup)
- [Flutter](https://firebase.google.com/docs/flutter/setup)
- [Unity](https://firebase.google.com/docs/unity/setup)
:::
Sau đó, bạn cần thiết lập Adapty SDK để liên kết người dùng với Firebase. Với mỗi người dùng, bạn cần gửi `firebase_app_instance_id` đến Adapty. Dưới đây là ví dụ về đoạn code có thể dùng để tích hợp Firebase SDK và Adapty SDK.
Bạn có thể thấy rằng một số sự kiện có tên được chỉ định sẵn, ví dụ như "Purchase", trong khi các sự kiện khác là sự kiện Adapty thông thường. Sự khác biệt này xuất phát từ [các loại sự kiện của Google Analytics](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference/events). Hiện tại, các sự kiện được hỗ trợ là [Refund](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference/events#refund) và [Purchase](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference/events#purchase). Các sự kiện khác là sự kiện tùy chỉnh. Vì vậy, hãy đảm bảo rằng tên sự kiện của bạn được [hỗ trợ](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=firebase#limitations) bởi Google Analytics.
Ngoài ra, bạn có thể thiết lập gửi thuộc tính người dùng trong Adapty dashboard.
Điều này có nghĩa là các sự kiện của bạn sẽ được Adapty bổ sung thêm `subscription_state` và `subscription_product_id`. Nhưng bạn cũng cần [bật](https://support.google.com/analytics/answer/14240153?hl=en) tính năng này trong Google Analytics. Vì vậy, để sử dụng **User properties** trong phân tích của bạn, hãy bắt đầu bằng cách gán chúng vào một custom dimension thông qua **Custom Definitions** trong Firebase Console bằng cách chọn **User scope**, đặt tên và mô tả cho chúng.
Hãy kiểm tra rằng tên thuộc tính người dùng của bạn là `subscription_state` và `subscription_product_id`. Nếu không, chúng tôi sẽ không thể gửi dữ liệu trạng thái gói đăng ký cho bạn.
Vậy là xong! Hãy chờ đón những thông tin chi tiết mới từ Google.
## Khắc phục sự cố \{#troubleshooting\}
### Sự chênh lệch dữ liệu \{#data-discrepancy\}
Nếu có sự chênh lệch dữ liệu giữa Adapty và Firebase, điều đó có thể xảy ra vì không phải tất cả người dùng của bạn đều sử dụng phiên bản ứng dụng có tích hợp Adapty SDK. Để đảm bảo tính nhất quán của dữ liệu, bạn có thể buộc người dùng cập nhật ứng dụng lên phiên bản có Adapty SDK.
Ngoài ra, các sự kiện sandbox được gửi đến Firebase theo mặc định và không thể tắt tính năng này. Vì vậy, trong các trường hợp ứng dụng có ít sự kiện Production và nhiều sự kiện Sandbox, có thể có sự chênh lệch đáng kể về số liệu giữa Analytics của Adapty và Firebase.
### Sự kiện hiển thị là đã gửi trong Adapty nhưng không có trong Firebase \{#events-are-shown-as-delivered-in-adapty-but-not-available-in-firebase\}
Có độ trễ thời gian giữa lúc sự kiện được gửi từ Adapty và lúc chúng xuất hiện trên Google Analytics Dashboard. Bạn nên theo dõi Realtime Dashboard trên tài khoản Google Analytics của mình để xem các sự kiện mới nhất theo thời gian thực.
---
# File: mixpanel
---
---
title: "Mixpanel"
description: "Kết nối Mixpanel với Adapty để phân tích gói đăng ký mạnh mẽ hơn."
---
[Mixpanel](https://mixpanel.com/home/) là một dịch vụ phân tích sản phẩm mạnh mẽ. Giải pháp theo dõi dựa trên sự kiện của nó giúp các nhóm sản phẩm có được những hiểu biết sâu sắc về chiến lược thu hút người dùng, chuyển đổi và giữ chân người dùng tối ưu trên các nền tảng khác nhau.
Tích hợp này cho phép bạn đưa tất cả các sự kiện Adapty vào Mixpanel. Nhờ đó, bạn sẽ có cái nhìn toàn diện hơn về hoạt động kinh doanh gói đăng ký và hành động của khách hàng. Adapty cung cấp bộ dữ liệu đầy đủ giúp bạn theo dõi [các sự kiện gói đăng ký](events) từ các cửa hàng ở một nơi. Với Adapty, bạn có thể dễ dàng thấy người dùng của mình đang hành xử như thế nào, tìm hiểu những gì họ thích và sử dụng thông tin đó để giao tiếp với họ theo cách có mục tiêu và hiệu quả.
## Cách thiết lập tích hợp Mixpanel \{#how-to-set-up-mixpanel-integration\}
1. Mở trang [Integrations -> Mixpanel](https://app.adapty.io/integrations/mixpanel) trong Adapty Dashboard.
2. Bật toggle và nhập **Mixpanel Token** của bạn. Bạn có thể chỉ định một token cho tất cả các nền tảng hoặc giới hạn cho các nền tảng cụ thể nếu bạn chỉ muốn nhận dữ liệu từ một số nền tảng nhất định.
3. Đặt **Mixpanel Data Residency** để khớp với dự án Mixpanel của bạn. Trường này bắt buộc và mặc định là **US**. Chọn **US** cho endpoint `api.mixpanel.com` hoặc **Europe** cho `api-eu.mixpanel.com`.
:::warning
Nếu dự án Mixpanel của bạn sử dụng data residency tại EU, bạn phải đặt **Mixpanel Data Residency** thành **Europe**. Mixpanel sẽ từ chối các sự kiện được gửi đến endpoint US từ các dự án EU.
:::
### Tìm Mixpanel Token của bạn \{#finding-your-mixpanel-token\}
Để lấy **Mixpanel Token** của bạn:
1. Đăng nhập vào [Mixpanel Dashboard](https://mixpanel.com/settings/project/) của bạn.
2. Mở **Settings** và chọn **Organization Settings**.
3. Từ thanh bên trái, đi đến **Projects** và chọn dự án của bạn.
## Cách tích hợp hoạt động \{#how-the-integration-works\}
Adapty tự động ánh xạ các thuộc tính sự kiện liên quan—như ID người dùng và doanh thu—sang [các thuộc tính gốc của Mixpanel](https://docs.mixpanel.com/docs/data-structure/user-profiles). Điều này đảm bảo việc theo dõi và báo cáo chính xác các sự kiện liên quan đến gói đăng ký.
Ngoài ra, Adapty tích lũy dữ liệu doanh thu theo từng người dùng và cập nhật [User Profile Properties](https://docs.mixpanel.com/docs/data-structure/user-profiles) của họ, bao gồm `subscription state` và `subscription product ID`. Sau khi nhận được một sự kiện, Mixpanel sẽ cập nhật các trường tương ứng theo thời gian thực.
## Sự kiện và thẻ \{#events-and-tags\}
Bên dưới phần thông tin xác thực, có ba nhóm sự kiện bạn có thể gửi đến Mixpanel từ Adapty. Chỉ cần bật những sự kiện bạn cần. Xem danh sách đầy đủ các sự kiện mà Adapty cung cấp [tại đây](events).
Chúng tôi khuyến nghị sử dụng tên sự kiện mặc định do Adapty cung cấp. Tuy nhiên, bạn có thể thay đổi tên sự kiện theo nhu cầu của mình.
## Cấu hình SDK \{#sdk-configuration\}
Sử dụng phương thức `.setIntegrationIdentifier()` để đặt `mixpanelUserId`. Nếu không đặt, Adapty sẽ sử dụng ID người dùng của bạn (`customerUserId`) hoặc nếu giá trị đó là null thì dùng Adapty ID. Hãy đảm bảo rằng ID người dùng bạn dùng để gửi dữ liệu đến Mixpanel từ ứng dụng của mình trùng với ID bạn gửi đến Adapty.
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
2. Đăng nhập vào [PostHog Dashboard](https://posthog.com/).
3. Điều hướng đến **Settings -> Project**.
4. Trong cửa sổ **Project**, cuộn xuống phần **Project ID** và sao chép **Project API key**.
5. Dán API key vào trường **Project API key** trong Adapty Dashboard. PostHog không có chế độ Sandbox riêng cho tích hợp server-to-server.
6. Chọn **PostHog Deployment** của bạn:
| Tùy chọn | Mô tả |
| -------- | ------------------------------------------------------------ |
| us/eu | Các deployment mặc định được PostHog lưu trữ. |
| Custom | Dành cho các instance tự lưu trữ. Nhập URL instance của bạn vào trường **PostHog Instance URL**. |
7. (Tùy chọn) Nếu bạn đang dùng deployment PostHog tự lưu trữ, nhập địa chỉ deployment của bạn vào trường **PostHog Instance URL**.
8. (Tùy chọn) Điều chỉnh các cài đặt như **Reporting Proceeds**, **Exclude Historical Events**, **Report User's Currency** và **Send Trial Price**. Xem [Cài đặt tích hợp](configuration#integration-settings) để biết thêm chi tiết về các tùy chọn này.
9. (Tùy chọn) Bạn cũng có thể tùy chỉnh các sự kiện nào được gửi tới PostHog trong phần **Events names**. Tắt các sự kiện không cần thiết hoặc đổi tên chúng theo nhu cầu.
10. Nhấn **Save** để hoàn tất thiết lập.
## Cấu hình SDK \{#sdk-configuration\}
Để bật tính năng nhận dữ liệu attribution từ PostHog, truyền giá trị `distinctId` vào Adapty như sau:
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
Mở tài khoản SplitMetrics Acquire của bạn, di chuột qua một trong các logo MMP và nhấn nút **Settings**. Tìm Client ID trong hộp thoại tại mục **5**, sao chép và dán vào Adapty tại trường **Client ID**.
Bạn cũng cần thiết lập Apple App ID để sử dụng tích hợp. Để tìm App ID, mở trang ứng dụng trong App Store Connect, vào trang **App Information** trong phần **General**, và tìm **Apple ID** ở góc dưới bên trái màn hình.
## Sự kiện và tag \{#events-and-tags\}
Bên dưới phần thông tin xác thực có ba nhóm sự kiện bạn có thể gửi từ Adapty đến SplitMetrics Acquire. Chỉ cần bật những sự kiện bạn cần. Xem danh sách đầy đủ các sự kiện mà Adapty cung cấp [tại đây](events).
Chúng tôi khuyến nghị sử dụng tên sự kiện mặc định do Adapty cung cấp. Tuy nhiên, bạn có thể thay đổi tên sự kiện theo nhu cầu. Adapty sẽ gửi các sự kiện gói đăng ký đến SplitMetrics Acquire thông qua tích hợp server-to-server, cho phép bạn xem toàn bộ sự kiện gói đăng ký trong dashboard SplitMetrics.
## Cấu hình SDK \{#sdk-configuration\}
Bạn không cần cấu hình gì thêm ở phía SDK, nhưng chúng tôi khuyến nghị gửi `customerUserId` đến Adapty để đảm bảo độ chính xác cao hơn.
:::warning
Hãy đảm bảo bạn đã cấu hình [Apple Search Ads](apple-search-ads) trong Adapty và [tải lên thông tin xác thực](https://app.adapty.io/settings/apple-search-ads). Nếu không có chúng, SplitMetrics Acquire sẽ không hoạt động.
:::
## Khắc phục sự cố \{#troubleshooting\}
Nếu tích hợp với SplitMetrics Acquire không hoạt động dù đã cấu hình đúng:
- Đảm bảo bạn đã bật toggle **Receive Apple Search Ads attribution in Adapty** trong [App Settings -> Apple Search Ads tab](https://app.adapty.io/settings/apple-search-ads), đã cấu hình [Apple Search Ads](apple-search-ads) trong Adapty và [tải lên thông tin xác thực](https://app.adapty.io/settings/apple-search-ads). Nếu không có chúng, SplitMetric sẽ không hoạt động.
- Đảm bảo các hồ sơ người dùng có attribution ASA không phải organic. Chỉ những hồ sơ người dùng có attribution ASA chi tiết, không phải organic mới gửi sự kiện đến Adapty.
## Cấu trúc sự kiện SplitMetrics Acquire \{#splitmetrics-acquire-event-structure\}
Adapty gửi sự kiện đến SplitMetrics Acquire qua GET request sử dụng query parameter. Mỗi sự kiện có cấu trúc như sau:
```json
{
"source": "Apple Search Ads",
"app_id": "123456789",
"name": "subscription_renewed",
"type": "subscription_renewed",
"revenue": 9.99,
"currency": "USD",
"tap_time": "2024-03-01 12:00:00",
"open_time": "2024-03-01 12:05:00",
"event_time": "2024-03-02 12:00:00",
"adaccount_id": "123456",
"campaign_id": "123456789",
"adgroup_id": "123456789",
"keyword_id": "123456789",
"creative_set_id": "123456789",
"Ad_id": "123456789",
"country_or_region": "US",
"conversion_type": "Download",
"user_id": "user_12345",
"att_status": "3",
"device_type": "iphone",
"app_version": "1.2.3",
"sdk_version": "2.10.0",
"ios_version": "17.2",
"event_value": "{\"vendor_product_id\":\"yearly.premium.6999\",\"original_transaction_id\":\"GPA.3383...\"}",
"event_id": "123e4567-e89b-12d3-a456-426614174000"
}
```
Trong đó:
| Tham số | Kiểu | Mô tả |
|:--------------------|:-------|:---------------------------------------------------------------------------------------------------------------------------|
| `source` | String | Luôn là "Apple Search Ads". |
| `app_id` | String | Apple App ID. |
| `name` | String | Tên sự kiện (ánh xạ từ sự kiện Adapty). |
| `type` | String | Loại sự kiện (giống với `name`). |
| `revenue` | Float | Số tiền doanh thu. |
| `currency` | String | Mã tiền tệ. |
| `tap_time` | String | Ngày và giờ nhấn vào quảng cáo. |
| `open_time` | String | Ngày và giờ mở ứng dụng (cài đặt). |
| `event_time` | String | Ngày và giờ xảy ra sự kiện. |
| `adaccount_id` | String | ID tổ chức ASA. |
| `campaign_id` | String | ID chiến dịch ASA. |
| `adgroup_id` | String | ID nhóm quảng cáo ASA. |
| `keyword_id` | String | ID từ khóa ASA. |
| `creative_set_id` | String | ID bộ sáng tạo ASA. |
| `Ad_id` | String | ID quảng cáo ASA. |
| `country_or_region` | String | Quốc gia hoặc khu vực của cửa hàng. |
| `conversion_type` | String | Loại chuyển đổi (ví dụ: "Download"). |
| `user_id` | String | Customer User ID hoặc Adapty Profile ID. |
| `att_status` | String | Trạng thái sử dụng tracking (0-3). |
| `device_type` | String | Loại thiết bị (ví dụ: "iphone", "ipad"). |
| `app_version` | String | Phiên bản ứng dụng. |
| `sdk_version` | String | Phiên bản Adapty SDK. |
| `ios_version` | String | Phiên bản iOS. |
| `event_value` | String | Chuỗi JSON chứa tất cả [chi tiết sự kiện](webhook-event-types-and-fields#for-most-event-types) hiện có. |
| `event_id` | String | ID sự kiện duy nhất (UUID). |
---
# File: braze
---
---
title: "Braze"
description: "Tích hợp Braze với Adapty để tối ưu hóa tương tác khách hàng và push notification."
---
Là một trong những giải pháp tương tác khách hàng hàng đầu, [Braze](https://www.braze.com/) cung cấp nhiều công cụ đa dạng cho push notification, email, SMS và in-app messaging. Bằng cách tích hợp Adapty với Braze, bạn có thể dễ dàng truy cập tất cả các sự kiện gói đăng ký của mình ở một nơi, cho phép kích hoạt giao tiếp tự động dựa trên các sự kiện đó.
Adapty cung cấp bộ dữ liệu đầy đủ giúp bạn theo dõi [sự kiện gói đăng ký](events) từ tất cả các cửa hàng ở một nơi và có thể dùng để cập nhật hồ sơ người dùng trong Braze. Với Adapty, bạn có thể dễ dàng theo dõi hành vi của người dùng, tìm hiểu sở thích của họ và sử dụng thông tin đó để giao tiếp theo cách có mục tiêu và hiệu quả. Do đó, tích hợp này cho phép bạn theo dõi các sự kiện gói đăng ký trong Braze dashboard và liên kết chúng với [các chiến dịch thu hút người dùng.](https://www.braze.com/product/journey-orchestration)
Adapty gửi các sự kiện gói đăng ký, thuộc tính người dùng và giao dịch mua sang Braze, giúp bạn xây dựng giao tiếp có mục tiêu với khách hàng qua push notification của Braze sau một quá trình tích hợp ngắn gọn và đơn giản như mô tả bên dưới.
## Cách thiết lập tích hợp Braze \{#how-to-set-up-braze-integration\}
Để tích hợp Braze, vào [Integrations -> Braze](https://app.adapty.io/integrations/braze), bật toggle và điền vào các trường thông tin.
Bước đầu tiên của quá trình tích hợp là cung cấp các thông tin xác thực cần thiết để thiết lập kết nối giữa hồ sơ Braze và Adapty của bạn. Bạn sẽ cần **REST API Key**, **Braze Instance ID** và **App IDs** cho iOS và Android để tích hợp hoạt động đúng cách:
1. **REST API Key** có thể được tạo trong **Braze Dashboard** → **Settings** → **API Keys**. Đảm bảo key của bạn có quyền `users.track` khi tạo:
2. Để lấy **Braze Instance ID**, hãy ghi lại URL Braze Dashboard của bạn và truy cập phần [Braze Docs](https://www.braze.com/docs/api/basics/#endpoints) nơi chỉ định instance ID. ID này có dạng khu vực như US-03, EU-01, v.v.
3. iOS và Android App IDs cũng có thể tìm thấy trong Braze Dashboard → Settings → API Keys. Sao chép chúng từ đây:
## Sự kiện, thuộc tính người dùng và giao dịch mua \{#events-user-attributes-and-purchases\}
Bên dưới phần thông tin xác thực, có ba nhóm sự kiện bạn có thể gửi từ Adapty sang Braze. Chỉ cần bật những sự kiện bạn cần. Bạn cũng có thể thay đổi tên các sự kiện theo nhu cầu trước khi gửi sang Braze. Xem danh sách đầy đủ các sự kiện do Adapty cung cấp [tại đây](events):
Adapty sẽ gửi các sự kiện gói đăng ký và thuộc tính người dùng sang Braze thông qua tích hợp server-to-server, cho phép bạn xem chúng trong Braze Dashboard và cấu hình các chiến dịch dựa trên đó.
Đối với các sự kiện có doanh thu, chẳng hạn như chuyển đổi dùng thử và gia hạn, Adapty sẽ gửi thông tin này sang Braze dưới dạng giao dịch mua.
[Tại đây](messaging#event-properties) bạn có thể tìm thấy thông số kỹ thuật đầy đủ về các thuộc tính sự kiện được gửi sang Braze.
:::note
Các thuộc tính người dùng hữu ích
Adapty gửi một số thuộc tính người dùng cho tích hợp Braze theo mặc định. Bạn có thể tham khảo danh sách bên dưới để xác định thuộc tính nào phù hợp nhất với nhu cầu của bạn.
:::
| Thuộc tính người dùng | Loại | Giá trị |
|--------------|----|-----|
| `adapty_customer_user_id` | String | Chứa giá trị của định danh duy nhất của người dùng do khách hàng định nghĩa. Có thể tìm thấy trong cả Adapty [Dashboard](profiles-crm) và Braze. |
| `adapty_profile_id` | String | Chứa giá trị của định danh duy nhất Adapty User Profile ID của người dùng, có thể tìm thấy trong Adapty [Dashboard](profiles-crm). |
| `environment` | String | Cho biết người dùng đang hoạt động trong môi trường sandbox hay production.
Giá trị là `Sandbox` hoặc `Production`
| | `store` | String |Chứa tên cửa hàng đã dùng để thực hiện giao dịch mua.
Các giá trị có thể có:
`app_store` hoặc `play_store`.
| | `vendor_product_id` | String |Chứa giá trị Product Id trong cửa hàng Apple/Google.
Ví dụ: org.locals.12345
| | `subscription_expires_at` | String |Chứa ngày hết hạn của gói đăng ký mới nhất.
Định dạng giá trị:
YYYY-MM-DDTHH:mm:ss.SSS+TZ
Ví dụ: 2023-02-15T17:22:03.000+0000
| | `active_subscription` | String | Giá trị sẽ được đặt thành `true` khi có bất kỳ sự kiện mua/gia hạn nào, hoặc `false` nếu gói đăng ký đã hết hạn. | | `period_type` | String |Cho biết loại kỳ mới nhất cho giao dịch mua hoặc gia hạn.
Các giá trị có thể có:
`trial` cho kỳ dùng thử hoặc `normal` cho các trường hợp còn lại.
| Tất cả giá trị float sẽ được làm tròn thành int. Chuỗi giữ nguyên. Ngoài danh sách các tag được định nghĩa sẵn, bạn cũng có thể gửi [thuộc tính tùy chỉnh](segments#custom-attributes) bằng cách sử dụng tag. Điều này mang lại sự linh hoạt hơn trong loại dữ liệu có thể đưa vào tag và có thể hữu ích để theo dõi thông tin cụ thể liên quan đến sản phẩm hoặc dịch vụ. Tất cả các thuộc tính người dùng tùy chỉnh đều được gửi tự động sang Braze nếu người dùng đánh dấu vào checkbox ** Send user attributes** từ [trang tích hợp](https://app.adapty.io/integrations/braze). ## Cấu hình SDK \{#sdk-configuration\} Để liên kết hồ sơ người dùng trong Adapty và Braze, bạn cần cấu hình Braze SDK với cùng customer user ID như Adapty hoặc sử dụng phương thức `.changeUser()` của nó:
2. Bật nút chuyển đổi tích hợp.
3. Nhập **OneSignal App ID** của bạn.
Để thiết lập tích hợp với OneSignal, vào [Integrations -> OneSignal](https://app.adapty.io/integrations/onesignal) trong Adapty dashboard của bạn, bật nút chuyển đổi và cấu hình thông tin xác thực tích hợp.
## Lấy OneSignal App ID của bạn \{#retrieving-your-onesignal-app-id\}
Tìm **OneSignal App ID** của bạn trong [OneSignal Dashboard](https://dashboard.onesignal.com/login):
1. Điều hướng đến **Settings** → **Keys & IDs**.
2. Sao chép **OneSignal App ID** của bạn và dán vào trường **App ID** trong Adapty Dashboard.
Bạn có thể tìm thêm thông tin về OneSignal ID trong [tài liệu sau.](https://documentation.onesignal.com/docs/en/keys-and-ids)
### Cấu hình sự kiện \{#configuring-events\}
Adapty cho phép bạn gửi ba nhóm sự kiện tới OneSignal. Bật những sự kiện bạn cần trong Adapty Dashboard. Bạn có thể xem danh sách đầy đủ các sự kiện có sẵn kèm mô tả chi tiết [tại đây](events).
Adapty gửi các sự kiện gói đăng ký tới OneSignal thông qua tích hợp server-to-server, cho phép bạn theo dõi toàn bộ hoạt động liên quan đến gói đăng ký trong OneSignal.
:::warning
Bắt đầu từ ngày 17 tháng 4 năm 2023, Gói Miễn phí của OneSignal không còn hỗ trợ tích hợp này. Tính năng này chỉ có trên các gói **Growth**, **Professional** và **cao hơn**. Để biết chi tiết, xem [Bảng giá OneSignal](https://onesignal.com/pricing).
:::
## Tag tùy chỉnh \{#custom-tags\}
Tích hợp này cập nhật và gán nhiều thuộc tính cho người dùng Adapty của bạn dưới dạng tag, sau đó được gửi tới OneSignal. Tham khảo danh sách tag bên dưới để tìm những tag phù hợp nhất với nhu cầu của bạn.
:::warning
OneSignal có giới hạn số lượng tag. Điều này bao gồm cả các tag do Adapty tạo ra và các tag hiện có trong OneSignal. Vượt quá giới hạn có thể gây ra lỗi khi gửi sự kiện.
:::
| Tag | Kiểu | Mô tả |
|---|----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `adapty_customer_user_id` | String | Mã định danh duy nhất của người dùng trong ứng dụng của bạn. Phải nhất quán trong toàn hệ thống, Adapty và OneSignal. |
| `adapty_profile_id` | String | ID hồ sơ người dùng Adapty, có sẵn trong [Adapty Dashboard](profiles-crm). |
| `environment` | String | `Sandbox` hoặc `Production`, cho biết môi trường hiện tại của người dùng. |
| `store` | String | Cửa hàng nơi sản phẩm được mua. Các tùy chọn: **app_store**, **play_store**, **stripe**, hoặc tên [cửa hàng tùy chỉnh](custom-store) của bạn. |
| `vendor_product_id` | String | ID sản phẩm trong cửa hàng ứng dụng (ví dụ: `org.locals.12345`). |
| `subscription_expires_at` | String | Ngày hết hạn của gói đăng ký mới nhất (`YYYY-MM-DDTHH:MM:SS+0000`, ví dụ: `2023-02-10T17:22:03.000000+0000`). |
| `last_event_type` | String | Loại sự kiện mới nhất từ [danh sách sự kiện Adapty](events).
1. **App ID** có thể tìm thấy trong dashboard Pushwoosh của bạn.
2. **Auth token** có thể tìm thấy trong phần API Access của Pushwoosh Settings.
## Sự kiện và tags \{#events-and-tags\}
Bên dưới phần thông tin xác thực có ba nhóm sự kiện bạn có thể gửi từ Adapty đến Pushwoosh. Chỉ cần bật những sự kiện bạn cần. Bạn cũng có thể thay đổi tên sự kiện theo nhu cầu trước khi gửi sang Pushwoosh. Xem danh sách đầy đủ các sự kiện mà Adapty cung cấp [tại đây](events).
Adapty sẽ gửi các sự kiện gói đăng ký đến Pushwoosh thông qua tích hợp server-to-server, cho phép bạn xem tất cả sự kiện gói đăng ký trong Pushwoosh Dashboard.
:::note
Custom tags
Với Adapty, bạn cũng có thể sử dụng custom tags cho tích hợp Pushwoosh. Bạn có thể tham khảo danh sách tags bên dưới để xác định tag nào phù hợp nhất với nhu cầu của mình.
:::
| Tag | Kiểu | Giá trị |
|---|----|-----|
| `adapty_customer_user_id` | String | Chứa giá trị định danh duy nhất của người dùng, có thể tìm thấy ở phía Pushwoosh. |
| `adapty_profile_id` | String | Chứa giá trị ID hồ sơ người dùng Adapty duy nhất của người dùng, có thể tìm thấy trong [dashboard](profiles-crm) Adapty của bạn. |
| `environment` | String | Cho biết người dùng đang hoạt động trong môi trường sandbox hay production.
Giá trị là `Sandbox` hoặc `Production`
| | `store` | String |Chứa tên cửa hàng được sử dụng để thực hiện giao dịch mua.
Các giá trị có thể có:
`app_store` hoặc `play_store`.
| | `vendor_product_id` | String |Chứa giá trị Product ID trong cửa hàng Apple/Google.
Ví dụ: org.locals.12345
| | `subscription_expires_at` | String |Chứa ngày hết hạn của gói đăng ký mới nhất.
Định dạng giá trị:
year-month dayThour:minute:second
Ví dụ: 2023-02-10T17:22:03.000000+0000
| | `last_event_type` | String | Cho biết loại sự kiện cuối cùng nhận được từ danh sách [sự kiện Adapty](events) tiêu chuẩn mà bạn đã bật cho tích hợp. | | `purchase_date` | String |Chứa ngày của giao dịch cuối cùng (mua lần đầu hoặc gia hạn).
Định dạng giá trị:
year-month dayThour:minute:second
Ví dụ: 2023-02-10T17:22:03.000000+0000
| | `original_purchase_date` | String |Chứa ngày mua lần đầu tiên theo giao dịch.
Định dạng giá trị:
year-month dayThour:minute:second
Ví dụ: 2023-02-10T17:22:03.000000+0000
| | `active_subscription` | String | Giá trị sẽ được đặt thành `true` khi có sự kiện mua/gia hạn, hoặc `false` nếu gói đăng ký đã hết hạn. | | `period_type` | String |Cho biết loại kỳ mới nhất cho giao dịch mua hoặc gia hạn.
Các giá trị có thể có:
`trial` cho giai đoạn dùng thử hoặc `normal` cho các trường hợp còn lại.
| Tất cả giá trị float sẽ được làm tròn thành int. Chuỗi string giữ nguyên. Ngoài danh sách tags có sẵn, bạn còn có thể gửi [thuộc tính tùy chỉnh](segments#custom-attributes) bằng tags. Điều này mang lại sự linh hoạt hơn về loại dữ liệu có thể đưa vào tag và hữu ích cho việc theo dõi thông tin cụ thể liên quan đến sản phẩm hoặc dịch vụ. Tất cả thuộc tính tùy chỉnh của người dùng sẽ được tự động gửi đến Pushwoosh nếu người dùng đánh dấu vào ô **Send user custom attributes** trên [trang tích hợp](https://app.adapty.io/integrations/pushwoosh). ## Cấu hình SDK \{#sdk-configuration\} Để liên kết Adapty với Pushwoosh, bạn cần gửi cho chúng tôi giá trị `HWID`:
2. Đặt tên tùy ý (ví dụ `Adapty`) và thêm vào workspace của bạn:
### 2\. Cấp quyền đăng tin nhắn và lấy token cho app \{#2-give-permission-to-post-and-get-a-token-for-your-app\}
Bạn sẽ được chuyển hướng đến trang app của mình trong Slack.
1. Kéo xuống và nhấn **Permissions**:
2. Sau khi chuyển hướng, kéo xuống phần **Scopes** và nhấn **Add an OAuth Scope**:
3. Cấp quyền `chat:write`, `chat:write.public` và `chat:write.customize`. Các quyền này cần thiết để đăng tin nhắn vào các kênh và tùy chỉnh nội dung tin nhắn:
4. Kéo lại lên đầu trang và nhấn **Install to Workspace**:
5. Nhấn **Allow** tại đây:
Sau bước này, bạn sẽ được chuyển về trang tương tự, nhưng lần này sẽ có OAuth Token (`xoxb-...`). Đây chính là thứ cần thiết để hoàn tất thiết lập:
### 3\. Cấu hình tích hợp trong Adapty \{#3-configure-the-integration-in-adapty\}
1. Truy cập [**Integrations** → **Slack**](https://app.adapty.io/integrations/slack):
2. Dán token `xoxb-...` từ bước trước và chọn các kênh mà app sẽ đăng tin nhắn vào. Bạn có thể thiết lập để chỉ nhận sự kiện từ môi trường production, sandbox hoặc cả hai. Bạn cũng có thể chọn đơn vị tiền tệ hiển thị (giữ nguyên hoặc quy đổi sang USD).
:::note
Lưu ý: nếu bạn muốn đăng tin nhắn từ Adapty vào một kênh riêng tư, bạn cần thêm thủ công app `Adapty` mà bạn đã tạo trong Slack vào kênh đó. Nếu không, tính năng này sẽ không hoạt động.
:::
3. Cuối cùng, bạn có thể chọn các sự kiện muốn nhận trong phần **Events**:
Vậy là xong!
Các sự kiện sẽ được gửi đến các kênh bạn đã chỉ định. Bạn sẽ thấy doanh thu khi có và có thể xem hồ sơ người dùng trong Adapty:
---
# File: s3-exports
---
---
title: "Amazon S3"
description: "Xuất dữ liệu gói đăng ký sang S3 để phân tích và báo cáo nâng cao."
---
Tích hợp Amazon S3 của Adapty cho phép bạn lưu trữ dữ liệu sự kiện và lượt truy cập paywall một cách an toàn tại một vị trí trung tâm. Bạn có thể lưu các [sự kiện gói đăng ký](events) vào Amazon S3 bucket của mình dưới dạng file .csv.
Để thiết lập tích hợp này, bạn cần thực hiện một vài bước đơn giản trong AWS Console và Adapty Dashboard.
:::note
Lịch trình
Adapty gửi dữ liệu của bạn mỗi **24h** lúc 4:00 UTC.
Mỗi file sẽ chứa dữ liệu cho các sự kiện được tạo trong toàn bộ ngày theo lịch trước đó theo UTC. Ví dụ: dữ liệu được xuất tự động lúc 4:00 UTC ngày 8 tháng 3 sẽ chứa tất cả các sự kiện được tạo vào ngày 7 tháng 3 từ 00:00:00 đến 23:59:59 theo UTC.
:::
## Cách thiết lập tích hợp Amazon S3 \{#how-to-set-up-amazon-s3-integration\}
Để bắt đầu nhận dữ liệu, bạn cần có các thông tin xác thực sau:
1. Access key ID
2. Secret access key
3. Tên S3 bucket
4. Tên thư mục bên trong S3 bucket
:::note
Thư mục lồng nhau
Bạn có thể chỉ định các thư mục lồng nhau trong trường tên Amazon S3 bucket, ví dụ: adapty-events/com.sample-app
:::
Để tích hợp Amazon S3, hãy vào [**Integrations** -> **Amazon S3**](https://app.adapty.io/integrations/s3), bật toggle từ tắt sang bật và điền vào các trường thông tin.
Trước tiên, hãy nhập thông tin xác thực để thiết lập kết nối giữa Amazon S3 và hồ sơ người dùng Adapty.
Trong Adapty Dashboard, các trường sau đây cần được điền để thiết lập kết nối:
| Trường | Mô tả |
| :--------------------------- | :----------------------------------------------------------- |
| **Access Key ID** | Một mã định danh duy nhất dùng để xác thực quyền truy cập của người dùng hoặc ứng dụng vào dịch vụ AWS. Tìm ID này trong [tệp csv](s3-exports#how-to-create-amazon-s3-credentials) đã tải xuống. |
| **Secret Access Key** | Khóa bí mật được dùng kết hợp với Access Key ID để xác thực quyền truy cập của người dùng hoặc ứng dụng vào dịch vụ AWS. Tìm khóa này trong [tệp csv](s3-exports#how-to-create-amazon-s3-credentials) đã tải xuống. |
| **S3 Bucket Name** | Tên duy nhất trên toàn cầu để xác định một S3 bucket cụ thể trong đám mây AWS. S3 bucket là dịch vụ lưu trữ đơn giản cho phép người dùng lưu trữ và truy xuất các đối tượng dữ liệu như tệp và hình ảnh trên đám mây. |
| **Folder Inside the Bucket** | Tên thư mục bạn muốn tạo bên trong S3 bucket đã chọn. Lưu ý rằng S3 mô phỏng các thư mục bằng cách sử dụng tiền tố khóa đối tượng, về cơ bản chính là tên thư mục. |
## Cách tạo thông tin xác thực Amazon S3 \{#how-to-create-amazon-s3-credentials\}
Hướng dẫn này sẽ giúp bạn tạo các thông tin xác thực cần thiết trong AWS Console.
### 1\. Tạo Access Policy \{#1-create-access-policy\}
Đầu tiên, truy cập [IAM Policy Dashboard](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/policies) trong AWS Console và chọn **Create Policy**.
In the Policy editor, paste the following JSON and change `adapty-s3-integration-test` to your bucket name:
```json showLineNumbers title="Json"
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListObjectsInBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::adapty-s3-integration-test"
},
{
"Sid": "AllowAllObjectActions",
"Effect": "Allow",
"Action": "s3:*Object",
"Resource": [
"arn:aws:s3:::adapty-s3-integration-test/*",
"arn:aws:s3:::adapty-s3-integration-test"
]
},
{
"Sid": "AllowBucketLocation",
"Effect": "Allow",
"Action": "s3:GetBucketLocation",
"Resource": "arn:aws:s3:::adapty-s3-integration-test"
}
]
}
```
Sau khi hoàn tất cấu hình policy, bạn có thể thêm tags (tùy chọn) rồi nhấn **Next** để chuyển sang bước cuối. Ở bước này, bạn đặt tên cho policy và nhấn **Create policy** để hoàn tất quá trình tạo.
### 2\. Tạo IAM user \{#create-iam-user\}
Để Adapty có thể tải các báo cáo dữ liệu thô lên bucket của bạn, bạn cần cung cấp cho họ Access Key ID và Secret Access Key của một user có quyền ghi vào bucket đó.
Để thực hiện, hãy truy cập IAM Console và chọn [mục Users](https://console.aws.amazon.com/iamv2/home#/users). Từ đó, nhấp vào nút **Add users**.
Đặt tên cho người dùng, chọn **Access key – Programmatic access**, rồi tiến hành cài đặt quyền.
Ở bước tiếp theo, hãy chọn tùy chọn **Add user to group** rồi nhấn nút **Create group**.
Tiếp theo, bạn cần đặt tên cho User Group và chọn policy mà bạn đã tạo trước đó. Sau khi chọn xong policy, nhấn vào nút **Create group** để hoàn tất quá trình.
Sau khi tạo nhóm thành công, vui lòng **chọn nhóm đó** và tiến hành bước tiếp theo.
Đây là bước cuối cùng của phần này, bạn chỉ cần nhấn vào nút **Create User** để tiếp tục.
Cuối cùng, bạn có thể **tải thông tin xác thực ở định dạng .csv** hoặc sao chép và dán trực tiếp từ dashboard.
## Xuất dữ liệu thủ công \{#manual-data-export\}
Ngoài tính năng tự động xuất dữ liệu sự kiện sang Amazon S3, Adapty còn cung cấp chức năng xuất file thủ công. Với tính năng này, bạn có thể chọn một khoảng thời gian cụ thể cho dữ liệu sự kiện và xuất thủ công sang S3 bucket của mình. Điều này giúp bạn kiểm soát tốt hơn dữ liệu cần xuất và thời điểm xuất.
Khoảng ngày được chỉ định sẽ được dùng để xuất các sự kiện được tạo từ Ngày A lúc 00:00:00 UTC đến Ngày B lúc 23:59:59 UTC.
## Cấu trúc bảng \{#table-structure\}
Trong tích hợp AWS S3, Adapty cung cấp một bảng để lưu trữ dữ liệu lịch sử cho các sự kiện giao dịch và lượt truy cập paywall. Bảng này chứa thông tin về hồ sơ người dùng, doanh thu và lợi nhuận, cửa hàng gốc, cùng nhiều điểm dữ liệu khác. Về cơ bản, các bảng này ghi lại toàn bộ giao dịch được tạo ra bởi một ứng dụng trong một khoảng thời gian nhất định.
:::warning
Lưu ý rằng cấu trúc này có thể mở rộng theo thời gian — với dữ liệu mới được chúng tôi hoặc các bên thứ ba mà chúng tôi hợp tác giới thiệu. Hãy đảm bảo rằng code xử lý dữ liệu của bạn đủ linh hoạt và chỉ phụ thuộc vào các trường cụ thể, không phụ thuộc vào toàn bộ cấu trúc.
:::
Dưới đây là cấu trúc bảng cho các sự kiện:
| Cột | Mô tả |
|---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **profile_id** | ID người dùng Adapty. |
| **event_type** | Tên sự kiện viết thường. Xem phần [Events](events) để tìm hiểu các loại sự kiện. |
| **event_datetime** | Ngày theo chuẩn ISO 8601. |
| **transaction_id** | Mã định danh duy nhất cho một giao dịch như mua hàng hoặc gia hạn. |
| **original_transaction_id** | Mã định danh giao dịch của lần mua hàng gốc. |
| **subscription_expires_at** | Ngày hết hạn của gói đăng ký. Thường là trong tương lai. |
| **environment** | Có thể là Sandbox hoặc Production. |
| **revenue_usd** | Doanh thu tính bằng USD. Có thể trống. |
| **proceeds_usd** | Tiền thu về tính bằng USD. Có thể trống. |
| **net_revenue_usd** | Doanh thu thuần (thu nhập sau thuế) tính bằng USD. Có thể trống. |
| **tax_amount_usd** | Số tiền khấu trừ cho thuế tính bằng USD. Có thể trống. |
| **revenue_local** | Doanh thu tính bằng đơn vị tiền tệ địa phương. Có thể trống. |
| **proceeds_local** | Tiền thu về tính bằng đơn vị tiền tệ địa phương. Có thể trống. |
| **net_revenue_local** | Doanh thu thuần (thu nhập sau thuế) tính bằng đơn vị tiền tệ địa phương. Có thể trống. |
| **tax_amount_local** | Số tiền khấu trừ cho thuế tính bằng đơn vị tiền tệ địa phương. Có thể trống. |
| **customer_user_id** | ID người dùng do nhà phát triển cung cấp. Ví dụ: có thể là UUID, email hoặc bất kỳ ID nào khác của người dùng. Null nếu bạn chưa thiết lập. |
| **store** | Có thể là _app_store_ hoặc _play_store_. |
| **product_id** | ID sản phẩm trên Apple App Store, Google Play Store hoặc Stripe. |
| **base_plan_id** | [ID gói cơ bản](https://support.google.com/googleplay/android-developer/answer/12154973) trên Google Play Store hoặc [ID giá](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) trên Stripe. |
| **developer_id** | ID nhà phát triển (SDK) của paywall nơi giao dịch bắt nguồn. |
| **ab_test_name** | Tên A/B test nơi giao dịch bắt nguồn. |
| **ab_test_revision** | Phiên bản của A/B test nơi giao dịch bắt nguồn. |
| **paywall_name** | Tên paywall nơi giao dịch bắt nguồn. |
| **paywall_revision** | Phiên bản của paywall nơi giao dịch bắt nguồn. |
| **profile_county** | Quốc gia của hồ sơ người dùng do Adapty xác định dựa trên địa chỉ IP. |
| **install_date** | Ngày cài đặt theo chuẩn ISO 8601. |
| **idfv** | [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor) trên thiết bị iOS |
| **idfa** | [advertisingIdentifier](https://developer.apple.com/documentation/adsupport/asidentifiermanager/advertisingidentifier) trên thiết bị iOS |
| **advertising_id** | Advertising ID là mã duy nhất do hệ điều hành Android cấp, mà các nhà quảng cáo có thể dùng để nhận dạng thiết bị của người dùng |
| **ip_address** | Địa chỉ IP của thiết bị (có thể là IPv4 hoặc IPv6, ưu tiên IPv4 nếu có). Được cập nhật mỗi khi địa chỉ IP của thiết bị thay đổi. |
| **cancellation_reason** | Lý do người dùng hủy gói đăng ký.
Có thể là:
**iOS & Android** _voluntarily_cancelled_, _billing_error_, _refund_
**iOS** _price_increase_, _product_was_not_available_, _unknown_, _upgraded_
**Android** _new_subscription_replace_, _cancelled_by_developer_
| | **android_app_set_id** | Một [AppSetId](https://developer.android.com/design-for-safety/privacy-sandbox/reference/adservices/appsetid/AppSetId) - ID duy nhất theo thiết bị, theo tài khoản nhà phát triển, có thể đặt lại bởi người dùng, dành cho các trường hợp quảng cáo không liên quan đến kiếm tiền. | | **android_id** | Trên Android 8.0 (API level 26) và các phiên bản cao hơn, đây là một số 64-bit (biểu diễn dưới dạng chuỗi thập lục phân), duy nhất cho mỗi tổ hợp khóa ký ứng dụng, người dùng và thiết bị. Xem thêm tại [tài liệu dành cho nhà phát triển Android](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID). | | **device** | Tên model thiết bị hiển thị cho người dùng. | | **currency** | Mã tiền tệ 3 chữ cái (ISO-4217) của giao dịch. | | **store_country** | Quốc gia của hồ sơ người dùng do Apple/Google store xác định. | | **attribution_source** | Nguồn attribution. | | **attribution_network_user_id** | ID do nguồn attribution cấp cho người dùng. | | **attribution_status** | Có thể là organic, non_organic hoặc unknown. | | **attribution_channel** | Tên kênh marketing. | | **attribution_campaign** | Tên chiến dịch marketing. | | **attribution_ad_group** | Nhóm quảng cáo attribution. | | **attribution_ad_set** | Bộ quảng cáo attribution. | | **attribution_creative** | Từ khóa creative của attribution. | | **attributes** | JSON của [thuộc tính người dùng tùy chỉnh](setting-user-attributes#custom-user-attributes). Bao gồm các thuộc tính tùy chỉnh mà bạn đã thiết lập để gửi từ ứng dụng mobile. Để gửi, hãy bật tùy chọn **Send User Attributes** trong trang [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). | | **integration_ids** | Tất cả các ID tích hợp liên kết với một hồ sơ người dùng. Dạng từ điển. Ví dụ: {'mixpanel_user_id': 'mixpanelUserId-test', 'facebook_anonymous_id': 'facebookAnonymousId-test'} | Here is the table structure for the paywall visits: | Cột | Mô tả | | :-------------------- | :----------------------------------------------------------------------------------------------------------------------- | | **profile_id** | ID người dùng Adapty. | | **customer_user_id** | ID người dùng do nhà phát triển đặt. Ví dụ: UUID, email, hoặc bất kỳ ID nào khác. Null nếu bạn chưa thiết lập. | | **profile_country** | Quốc gia của hồ sơ người dùng được xác định bởi cửa hàng Apple/Google. | | **install_date** | Ngày cài đặt theo định dạng ISO 8601. | | **store** | Có thể là _app_store_ hoặc _play_store_. | | **paywall_showed_at** | Ngày paywall được hiển thị cho khách hàng. | | **developer_id** | ID (SDK) của nhà phát triển cho paywall nơi giao dịch bắt nguồn. | | **ab_test_name** | Tên của A/B test nơi giao dịch bắt nguồn. | | **ab_test_revision** | Phiên bản của A/B test nơi giao dịch bắt nguồn. | | **paywall_name** | Tên của paywall nơi giao dịch bắt nguồn. | | **paywall_revision** | Phiên bản của paywall nơi giao dịch bắt nguồn. | ## Sự kiện và thẻ tag \{#events-and-tags\} Bạn có thể quản lý dữ liệu nào được truyền qua integration. Integration cung cấp các tùy chọn cấu hình sau: | Cài đặt | Mô tả | | :--------------------------------- | :----------------------------------------------------------- | | **Exclude Historical Events** | Chọn để loại trừ các sự kiện đã xảy ra trước khi người dùng cài đặt ứng dụng có tích hợp Adapty SDK. Điều này giúp tránh trùng lặp sự kiện và đảm bảo báo cáo chính xác. Ví dụ: nếu người dùng kích hoạt gói đăng ký hàng tháng vào ngày 10 tháng 1 và cập nhật ứng dụng có Adapty SDK vào ngày 6 tháng 3, Adapty sẽ bỏ qua các sự kiện trước ngày 6 tháng 3 và chỉ giữ lại các sự kiện sau đó. | | **Include events without profile** | Chọn để bao gồm các giao dịch không được liên kết với hồ sơ người dùng trong Adapty. Những giao dịch này có thể bao gồm các sản phẩm mua một lần được thực hiện trước khi cài đặt Adapty SDK hoặc các giao dịch nhận được từ thông báo máy chủ của cửa hàng mà chưa thể liên kết ngay với một người dùng cụ thể. | | **Send User Attributes** | Nếu bạn muốn gửi các thuộc tính riêng của người dùng, chẳng hạn như tùy chọn ngôn ngữ, và gói OneSignal của bạn hỗ trợ hơn 10 tag, hãy chọn tùy chọn này. Khi bật tùy chọn này, bạn có thể gửi thêm thông tin ngoài 10 tag mặc định. Lưu ý rằng việc vượt quá giới hạn tag có thể dẫn đến lỗi. |
Bên dưới cài đặt tích hợp, có ba nhóm sự kiện bạn có thể xuất, gửi và lưu trữ trong Amazon S3 từ Adapty. Chỉ cần bật những nhóm bạn cần. Xem danh sách đầy đủ các sự kiện mà Adapty cung cấp [tại đây](events).
---
# File: google-cloud-storage
---
---
title: "Google Cloud Storage"
description: "Tích hợp Google Cloud Storage với Adapty để lưu trữ dữ liệu an toàn."
---
Bật tích hợp Google Cloud Storage để lưu trữ an toàn [các sự kiện gói đăng ký](events) và [dữ liệu lượt xem paywall](paywall-metrics) tại một nơi duy nhất: bucket Google Cloud Storage của bạn.
Mỗi ngày lúc 4 giờ sáng UTC, Adapty sẽ tải lên các file .csv chứa dữ liệu của ngày hôm trước vào bucket của bạn. Bạn có thể chọn nhận dữ liệu **sự kiện**, dữ liệu **lượt xem paywall**, hoặc **cả hai**. Bạn cũng có thể xuất dữ liệu này [thủ công](#manual-data-export) bất cứ lúc nào, cho bất kỳ khoảng thời gian nào.
Để thiết lập tích hợp, [tạo khóa truy cập bucket](#create-google-cloud-storage-credentials) trong Google Cloud Console, sau đó [thêm vào cài đặt Adapty của bạn](#set-up-google-cloud-storage-integration).
## Lịch tải lên và thời gian xử lý \{#upload-schedule-and-duration\}
Adapty tải dữ liệu lên Google Cloud Storage mỗi 24 giờ, lúc 04:00 UTC.
Các file chứa dữ liệu cho các sự kiện được tạo trong ngày dương lịch trước đó (UTC). File được tải lên vào ngày 8 tháng 3 sẽ chứa tất cả sự kiện được tạo vào ngày 7 tháng 3, từ 00:00:00 đến 23:59:59 UTC.
Quá trình này có thể mất đến vài giờ, tùy thuộc vào tổng số file trong hàng đợi cũng như lượng dữ liệu bạn yêu cầu. Nếu Adapty đưa dữ liệu lịch sử vào lần tải lên đầu tiên, thời gian xử lý sẽ lâu hơn so với các lần tải lên hàng ngày sau đó.
## Thiết lập tích hợp Google Cloud Storage \{#set-up-google-cloud-storage-integration\}
Bạn cần có khóa tài khoản dịch vụ Google Cloud hợp lệ với **quyền ghi**. Để tạo khóa, làm theo các bước trong phần [tạo thông tin xác thực](#create-google-cloud-storage-credentials).
:::warning
Bạn có thể dùng các bucket khác nhau với thông tin xác thực khác nhau cho sự kiện và lượt xem paywall. Tuy nhiên, nếu **một trong hai** bộ thông tin xác thực không hợp lệ, [**cả hai lần tải lên đều sẽ thất bại**](#troubleshooting).
:::
Vào [**Integrations** -> **Google Cloud Storage**](https://app.adapty.io/integrations/google-cloud-storage), mở tab cần thiết (**Events** hoặc **Paywall visits**). Bật tích hợp.
Tải lên file chứa **khóa tài khoản dịch vụ Google Cloud** của bạn. Chỉ định **bucket** và **folder** đích. Lưu các thay đổi.
### Cài đặt tùy chọn cho dữ liệu sự kiện \{#optional-settings-for-event-data\}
Bạn có thể chỉ định những sự kiện nào được đưa vào báo cáo và đặt tên tùy chỉnh cho các sự kiện. Xem bài viết [events](events) để biết danh sách đầy đủ các sự kiện khả dụng.
| Tên | Mặc định | Mô tả |
| ------------------------------ | ----------------- | ----------- |
| Exclude historical events | true | Loại trừ thông tin về các sự kiện xảy ra trước khi bạn tích hợp Adapty SDK vào ứng dụng. Một người dùng đã mua gói đăng ký hàng tháng vào ngày 10 tháng 1. Bản cập nhật ngày 1 tháng 3 của ứng dụng là bản đầu tiên tích hợp Adapty SDK.
Nếu cài đặt này **bật**, báo cáo sẽ không bao gồm sự kiện "subscription started" từ tháng 1, cũng như sự kiện "subscription renewed" từ tháng 2. Nhưng **sẽ** bao gồm sự kiện "subscription renewed" từ ngày 10 tháng 3.
Lý do người dùng hủy gói đăng ký.
Các giá trị có thể:
**iOS & Android** — *voluntarily_cancelled*, *billing_error*, *refund*
**Chỉ iOS** — *price_increase*, *product_was_not_available*, *unknown*, *upgraded*
**Chỉ Android** — *new_subscription_replace*, *cancelled_by_developer*
| | **android_app_set_id** | Một [AppSetId](https://developer.android.com/design-for-safety/privacy-sandbox/reference/adservices/appsetid/AppSetId) - ID duy nhất theo từng thiết bị, theo từng tài khoản nhà phát triển, có thể đặt lại bởi người dùng, dùng cho các trường hợp quảng cáo không phát sinh doanh thu. | | **android_id** | Trên Android 8.0 (API level 26) trở lên, đây là số 64-bit (biểu diễn dưới dạng chuỗi thập lục phân), duy nhất cho mỗi tổ hợp khóa ký ứng dụng, người dùng và thiết bị. Xem thêm tại [tài liệu dành cho nhà phát triển Android](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID). | | **device** | Tên model thiết bị hiển thị cho người dùng. | | **currency** | Mã tiền tệ 3 chữ cái (ISO-4217) của giao dịch. | | **store_country** | Quốc gia của hồ sơ người dùng được xác định bởi cửa hàng Apple/Google. | | **attribution_source** | Nguồn attribution. | | **attribution_network_user_id** | ID được nguồn attribution gán cho người dùng. | | **attribution_status** | Có thể là organic, non_organic hoặc unknown. | | **attribution_channel** | Tên kênh marketing. | | **attribution_campaign** | Tên chiến dịch marketing. | | **attribution_ad_group** | Nhóm quảng cáo attribution. | | **attribution_ad_set** | Bộ quảng cáo attribution. | | **attribution_creative** | Từ khóa sáng tạo attribution. | | **attributes** | JSON của [thuộc tính người dùng tùy chỉnh](setting-user-attributes#custom-user-attributes). Bao gồm mọi thuộc tính tùy chỉnh bạn đã thiết lập để gửi từ ứng dụng di động. Để gửi, bật tùy chọn **Send User Attributes** trong trang [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). | | **integration_ids** | Tất cả ID tích hợp liên kết với một hồ sơ người dùng. Dạng Dictionary. Ví dụ: {'mixpanel_user_id': 'mixpanelUserId-test', 'facebook_anonymous_id': 'facebookAnonymousId-test'} | ### Lượt xem paywall \{#paywall-visits\} | Cột | Mô tả | | :-------------------- | :----------------------------------------------------------------------------------------------------------- | | **profile_id** | ID người dùng Adapty. | | **customer_user_id** | ID người dùng do nhà phát triển đặt. Ví dụ: UUID người dùng, email hoặc ID bất kỳ. Null nếu bạn chưa thiết lập. | | **profile_country** | Quốc gia của hồ sơ người dùng được xác định bởi cửa hàng Apple/Google. | | **install_date** | Ngày cài đặt theo định dạng ISO 8601. | | **store** | Có thể là *app_store* hoặc *play_store*. | | **paywall_showed_at** | Ngày paywall được hiển thị cho khách hàng. | | **developer_id** | ID nhà phát triển (SDK) của paywall nơi giao dịch bắt nguồn. | | **ab_test_name** | Tên A/B test nơi giao dịch bắt nguồn. | | **ab_test_revision** | Phiên bản của A/B test nơi giao dịch bắt nguồn. | | **paywall_name** | Tên paywall nơi giao dịch bắt nguồn. | | **paywall_revision** | Phiên bản của paywall nơi giao dịch bắt nguồn. | ## Xử lý sự cố \{#troubleshooting\} Adapty kiểm tra tính hợp lệ của khóa truy cập **trước khi** bắt đầu tải lên. Ngay cả khi chỉ một trong các khóa Google Cloud Storage không hợp lệ, Adapty **sẽ hủy toàn bộ quá trình tải lên** và báo lỗi. Để đảm bảo quá trình tải lên không bị gián đoạn, hãy thay thế khóa trước khi hết hạn. Nếu bạn cập nhật khóa cho **sự kiện**, đừng quên cập nhật khóa cho **lượt xem paywall**, và ngược lại. --- # File: webhook-event-types-and-fields --- --- title: "Các loại sự kiện và trường dữ liệu webhook" description: "" --- Adapty gửi webhook để phản hồi các sự kiện gói đăng ký. Phần này định nghĩa các loại sự kiện và dữ liệu có trong mỗi webhook. ## Các loại sự kiện Webhook \{#webhook-event-types\} Bạn có thể gửi tất cả các loại sự kiện đến webhook của mình hoặc chỉ chọn một số loại nhất định. Tham khảo [Event flows](event-flows) để tìm hiểu loại dữ liệu đầu vào cần mong đợi và cách xây dựng logic nghiệp vụ xung quanh đó. Bạn có thể tắt các loại sự kiện không cần thiết khi [thiết lập tích hợp Webhook](set-up-webhook-integration#configure-webhook-integration-in-the-adapty-dashboard). Tại đó, bạn cũng có thể thay thế ID sự kiện mặc định của Adapty bằng ID của riêng mình nếu cần. | Tên sự kiện | Mô tả | |:-----------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | subscription_started | Kích hoạt khi người dùng bắt đầu gói đăng ký trả phí không có thời gian dùng thử, tức là bị tính phí ngay lập tức. | | subscription_renewed | Xảy ra khi gói đăng ký được gia hạn và người dùng bị tính phí. Sự kiện này bắt đầu từ lần thanh toán thứ hai, dù là gói đăng ký có hay không có dùng thử. | | subscription_renewal_cancelled | Người dùng đã tắt tính năng tự động gia hạn gói đăng ký. Người dùng vẫn có thể sử dụng các tính năng cao cấp cho đến khi kết thúc chu kỳ đăng ký đã thanh toán. | | subscription_renewal_reactivated | Kích hoạt khi người dùng bật lại tính năng tự động gia hạn gói đăng ký. | | subscription_expired | Kích hoạt khi gói đăng ký hết hạn hoàn toàn sau khi bị hủy. Ví dụ: nếu người dùng hủy gói đăng ký vào ngày 12 tháng 12 nhưng vẫn còn hiệu lực đến ngày 31 tháng 12, sự kiện sẽ được ghi nhận vào ngày 31 tháng 12 khi gói đăng ký hết hạn. | | subscription_paused | Xảy ra khi người dùng kích hoạt tính năng [tạm dừng gói đăng ký](https://developer.android.com/google/play/billing/lifecycle/subscriptions#pause) (chỉ dành cho Android). | | subscription_deferred | Kích hoạt khi giao dịch mua gói đăng ký được [hoãn lại](https://adapty.io/glossary/subscription-purchase-deferral/), cho phép người dùng trì hoãn việc thanh toán trong khi vẫn duy trì quyền truy cập các tính năng cao cấp. Tính năng này có sẵn thông qua Google Play Developer API và có thể dùng cho các bản dùng thử miễn phí hoặc hỗ trợ người dùng gặp khó khăn về tài chính. | | non_subscription_purchase | Bất kỳ sản phẩm mua một lần nào, chẳng hạn như quyền truy cập trọn đời hoặc các sản phẩm consumable như tiền trong game. | | trial_started | Kích hoạt khi người dùng bắt đầu gói đăng ký dùng thử. | | trial_converted | Xảy ra khi thời gian dùng thử kết thúc và người dùng bị tính phí (lần mua đầu tiên). Ví dụ: nếu người dùng có thời gian dùng thử đến ngày 14 tháng 1 nhưng bị tính phí vào ngày 7 tháng 1, sự kiện sẽ được ghi nhận vào ngày 7 tháng 1. | | trial_renewal_cancelled | Người dùng đã tắt tính năng tự động gia hạn trong thời gian dùng thử. Người dùng vẫn có thể sử dụng các tính năng cao cấp cho đến khi thời gian dùng thử kết thúc nhưng sẽ không bị tính phí hay chuyển sang gói đăng ký. | | trial_renewal_reactivated | Xảy ra khi người dùng bật lại tính năng tự động gia hạn trong thời gian dùng thử. | | trial_expired | Kích hoạt khi thời gian dùng thử kết thúc mà không chuyển sang gói đăng ký. | | entered_grace_period | Xảy ra khi một lần thanh toán thất bại và người dùng bước vào thời gian ân hạn (nếu được bật). Người dùng vẫn có thể truy cập các tính năng cao cấp trong thời gian này. | | billing_issue_detected | Kích hoạt khi xảy ra sự cố thanh toán trong quá trình thực hiện giao dịch (ví dụ: số dư thẻ không đủ). | | subscription_refunded | Kích hoạt khi gói đăng ký được hoàn tiền (ví dụ: do Apple Support xử lý). | | non_subscription_purchase_refunded | Kích hoạt khi một sản phẩm mua một lần được hoàn tiền. | | access_level_updated | Xảy ra khi mức độ truy cập của người dùng được cập nhật. | :::note `subscription_renewal_reactivated` mang **ID sản phẩm trước đó** — tức là sản phẩm đang hoạt động khi người dùng hủy — ngay cả khi người dùng sau đó kích hoạt lại bằng cách mua một sản phẩm khác. Apple giữ nguyên `original_transaction_id` trong toàn bộ chuỗi hủy → kích hoạt lại, vì vậy sự kiện này phản ánh sản phẩm ban đầu. Sản phẩm mới sẽ xuất hiện trong sự kiện `subscription_renewed` tiếp theo khi việc tính phí cho sản phẩm mới bắt đầu. ::: ## Cấu trúc sự kiện Webhook \{#webhook-event-structure\} Adapty chỉ gửi những sự kiện mà bạn đã chọn trong phần **Events names** của trang [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). Các sự kiện webhook được tuần tự hóa dưới dạng JSON. Phần thân của yêu cầu `POST` gửi đến máy chủ của bạn sẽ chứa sự kiện đã được tuần tự hóa, được đóng gói trong cấu trúc dưới đây. Tất cả các sự kiện đều có cùng cấu trúc, nhưng các trường dữ liệu sẽ khác nhau tùy theo loại sự kiện, cửa hàng và cấu hình cụ thể của bạn. Thuộc tính người dùng là các [thuộc tính người dùng tùy chỉnh](setting-user-attributes#custom-user-attributes) mà bạn đã thiết lập, vì vậy chúng chứa những gì bạn đã cấu hình. Các trường dữ liệu attribution cũng giống nhau cho tất cả các loại sự kiện, tuy nhiên danh sách các attribution sẽ phụ thuộc vào nguồn attribution bạn sử dụng trong ứng dụng di động của mình. Xem bên dưới ví dụ về một sự kiện: ```json title="Json" showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem", "idfv": "00000000-0000-0000-0000-000000000000", "idfa": "00000000-0000-0000-0000-000000000000", "advertising_id": "00000000-0000-0000-0000-000000000000", "profile_install_datetime": "2000-01-31T00:00:00.000000+0000", "user_agent": "ExampleUserAgent/1.0 (Device; OS Version) Browser/Engine", "email": "john.doe@company.com", "event_type": "subscription_started", "event_datetime": "2000-01-31T00:00:00.000000+0000", "event_properties": { "store": "play_store", "currency": "USD", "price_usd": 4.99, "profile_id": "00000000-0000-0000-0000-000000000000", "cohort_name": "All Users", "environment": "Production", "price_local": 4.99, "base_plan_id": "b1", "developer_id": "onboarding_placement", "ab_test_name": "onboarding_ab_test", "ab_test_revision": 1, "paywall_name": "UsedPaywall", "proceeds_usd": 4.2315, "variation_id": "00000000-0000-0000-0000-000000000000", "purchase_date": "2024-11-15T10:45:36.181000+0000", "store_country": "AR", "event_datetime": "2000-01-31T00:00:00.000000+0000", "proceeds_local": 4.2415, "tax_amount_usd": 0, "transaction_id": "0000000000000000", "net_revenue_usd": 4.2415, "profile_country": "AR", "paywall_revision": "1", "profile_event_id": "00000000-0000-0000-0000-000000000000", "tax_amount_local": 0, "net_revenue_local": 4.2415, "vendor_product_id": "onemonth_no_trial", "profile_ip_address": "10.10.1.1", "consecutive_payments": 1, "rate_after_first_year": false, "original_purchase_date": "2000-01-31T00:00:00.000000+0000", "original_transaction_id": "0000000000000000", "subscription_expires_at": "2000-01-31T00:00:00.000000+0000", "profile_has_access_level": true, "profile_total_revenue_usd": 4.99, "promotional_offer_id": null, "store_offer_category": null, "store_offer_discount_type": null }, "event_api_version": 1, "profiles_sharing_access_level": [{"profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem"}], "attributions": { "appsflyer": { "ad_set": "Keywords 1.12", "status": "non_organic", "channel": "Google Ads", "ad_group": null, "campaign": "Social media influencers - Rest of the world", "creative": null, "created_at": "2000-01-31T00:00:00.000000+0000" } }, "user_attributes": {"Favourite_color": "Violet", "Pet_name": "Fluffy"}, "integration_ids": {"firebase_app_instance_id": "val1", "branch_id": "val2", "one_signal_player_id": "val3"}, "play_store_purchase_token": { "product_id": "product_123", "purchase_token": "token_abc_123", "is_subscription": true } } ``` ### Các trường của sự kiện \{#event-fields\} Các tham số sự kiện đều giống nhau cho tất cả các loại sự kiện. | **Trường** | **Kiểu** | **Mô tả** | |---|---|---| | **advertising_id** | UUID | ID quảng cáo (chỉ dành cho Android). | | **attributions** | JSON | [Dữ liệu attribution](webhook-event-types-and-fields#attributions). Được đính kèm nếu **Send Attribution** được bật trong [Webhook settings](https://app.adapty.io/integrations/customwebhook). | | **customer_user_id** | String | ID người dùng từ app của bạn (UUID, email, hoặc ID khác) nếu bạn đã thiết lập trong code app khi [xác định người dùng](ios-quickstart-identify). Nếu bạn không xác định người dùng trong code app hoặc người dùng cụ thể này là ẩn danh (chưa đăng nhập), trường này sẽ là `null`. | | **email** | String | Email của người dùng nếu bạn thiết lập bằng phương thức [`updateProfile`](setting-user-attributes) trong Adapty SDK hoặc khi tạo/cập nhật hồ sơ người dùng qua server-side API. Nếu bạn không truyền giá trị `email` vào SDK hoặc phương thức API, trường này sẽ là `null`. | | **event_api_version** | Integer | Phiên bản Adapty API (hiện tại: `1`). | | **event_datetime** | ISO 8601 | Thời điểm thực tế (nghiệp vụ) của sự kiện, chẳng hạn ngày mua hàng đối với giao dịch mua hoặc ngày hết hạn đối với sự kiện hết hạn — không phải thời điểm Adapty nhận hoặc gửi sự kiện. Định dạng [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) (ví dụ: `2020-07-10T15:00:00.000000+0000`). Xem ghi chú bên dưới về thứ tự sắp xếp. | | **event_properties** | JSON | [Thuộc tính sự kiện](webhook-event-types-and-fields#event-properties). | | **event_type** | String | Tên sự kiện theo định dạng Adapty. Xem [Loại sự kiện Webhook](webhook-event-types-and-fields#webhook-event-types) để biết danh sách đầy đủ. | | **idfa** | UUID | ID quảng cáo (chỉ dành cho Apple). **IDFA** trong hồ sơ người dùng trên [Adapty Dashboard](https://app.adapty.io/profiles/users). Có thể là `null` nếu không khả dụng do hạn chế theo dõi, chế độ trẻ em, hoặc cài đặt quyền riêng tư. | | **idfv** | UUID | Identifier for Vendors (IDFV), duy nhất theo nhà phát triển. **IDFV** trong hồ sơ người dùng trên [Adapty Dashboard](https://app.adapty.io/profiles/users). | | **integration_ids** | JSON | ID tích hợp người dùng nếu bạn thiết lập bằng phương thức `setIntegrationIdentifier` trong Adapty SDK hoặc khi tạo/cập nhật hồ sơ người dùng qua server-side API. `null` nếu không khả dụng hoặc các tích hợp bị tắt. | | **play_store_purchase_token** | JSON | [Token mua hàng Play Store](webhook-event-types-and-fields#play-store-purchase-token), được đính kèm nếu **Send Play Store purchase token** được bật trong [Webhook settings](https://app.adapty.io/integrations/customwebhook). | | **profile_id** | UUID | ID hồ sơ người dùng được Adapty tự động tạo cho mỗi hồ sơ. Một Apple/Google ID có thể được liên kết với nhiều profile ID khác nhau nếu bạn không xác định người dùng hoặc cho phép mua hàng trước khi đăng nhập. Tìm hiểu [thêm về cách Adapty hoạt động với hồ sơ cha/con](how-profiles-work#parent-and-inheritor-profiles). | | **profile_install_datetime** | ISO 8601 | Thời điểm cài đặt theo định dạng [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) (ví dụ: `2020-07-10T15:00:00.000000+0000`). | | **profiles_sharing_access_level** | JSON | Danh sách người dùng [chia sẻ mức độ truy cập](general#6-sharing-paid-access-between-user-accounts) ngoại trừ hồ sơ người dùng hiện tại. Nếu tính năng chia sẻ mức độ truy cập được bật cho app của bạn, danh sách này bao gồm các hồ sơ khác đã được sử dụng với cùng Apple/Google ID.Trong khi các giá trị thuộc tính tùy chỉnh trong code app di động có thể được thiết lập dưới dạng float hoặc string, các thuộc tính nhận được qua server-side API hoặc import lịch sử có thể có định dạng khác. Trong trường hợp này, các giá trị boolean và integer sẽ được chuyển đổi thành float.
| :::note `event_datetime` phản ánh thời điểm một sự kiện xảy ra trong vòng đời gói đăng ký, chứ không phải thời điểm Adapty xử lý hay gửi nó. Vì vậy, các sự kiện có thể có cùng `event_datetime` hoặc đến không theo thứ tự thời gian. Ví dụ, một sự kiện `subscription_expired` có thể mang `event_datetime` sớm hơn một sự kiện `subscription_renewal_cancelled` mà Adapty gửi trước nó. Đừng dựa vào `event_datetime` để sắp xếp thứ tự các sự kiện. Thay vào đó, hãy sắp xếp sự kiện theo thời gian bạn nhận được, và loại bỏ trùng lặp bằng `profile_event_id` hoặc các transaction ID. ::: ### Attribution \{#attributions\} Để gửi dữ liệu attribution, hãy bật tùy chọn **Send Attribution** trong trang [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). Nếu bạn đã bật tính năng gửi dữ liệu attribution và đã thiết lập [tích hợp attribution](attribution-integration), dữ liệu dưới đây sẽ được gửi kèm theo sự kiện cho mỗi nguồn. Cùng một dữ liệu attribution sẽ được gửi cho tất cả các loại sự kiện. ```json title="Json" showLineNumbers { "attributions": { "appsflyer": { "ad_set": "sample_ad_set_123", "status": "non_organic", "channel": "sample_channel", "ad_group": "sample_ad_group_456", "campaign": "sample_ios_campaign", "creative": "sample_creative_789", "created_at": "2000-01-31T00:00:00.000000+0000", "network_user_id": "0000000000000-0000000" } } } ``` | Tên trường | Kiểu dữ liệu | Mô tả | | :------------------ | :------------ | :------------------------------------------------- | | **ad_set** | String | Ad set attribution. | | **status** | String | Có thể là `organic`, `non_organic,` hoặc `unknown`. | | **channel** | String | Tên kênh marketing. | | **ad_group** | String | Ad group attribution. | | **campaign** | String | Tên chiến dịch marketing. | | **creative** | String | Từ khóa creative của attribution. | | **created_at** | ISO 8601 date | Ngày và giờ tạo bản ghi attribution. | | **network_user_id** | String | ID được nguồn attribution gán cho người dùng. | ### ID tích hợp \{#integration-ids\} Các ID tích hợp sau đây hiện được sử dụng trong các sự kiện: - `adjust_device_id` - `airbridge_device_id` - `amplitude_device_id` - `amplitude_user_id` - `appmetrica_device_id` - `appmetrica_profile_id` - `appsflyer_id` - `branch_id` - `facebook_anonymous_id` - `firebase_app_instance_id` - `mixpanel_user_id` - `pushwoosh_hwid` - `one_signal_player_id` - `one_signal_subscription_id` - `tenjin_analytics_installation_id` - `posthog_distinct_user_id` ### Mã token mua hàng trên Play Store \{#play-store-purchase-token\} Trường này chứa toàn bộ dữ liệu cần thiết để xác thực lại giao dịch khi cần. Nó chỉ được gửi khi tùy chọn **Send Play Store purchase token** được bật trong [cài đặt tích hợp Webhook](https://app.adapty.io/integrations/customwebhook). | Field | Type | Description | | :------------------ | :------ | :----------------------------------------------------------- | | **product_id** | String | Mã định danh duy nhất của sản phẩm (SKU) được mua trên Play Store. | | **purchase_token** | String | Token do Google Play tạo ra để xác định duy nhất giao dịch mua này. | | **is_subscription** | Boolean | Cho biết sản phẩm được mua là gói đăng ký (`true`) hay sản phẩm mua một lần (`false`). | ### Thuộc tính sự kiện \{#event-properties\} Thuộc tính sự kiện có thể khác nhau tùy theo loại sự kiện và thậm chí giữa các sự kiện cùng loại. Ví dụ, một sự kiện từ App Store sẽ không bao gồm các thuộc tính dành riêng cho Android như `base_plan_id`. Sự kiện [Access Level Updated](webhook-event-types-and-fields#for-access-level-updated-event) có các thuộc tính riêng biệt, vì vậy chúng tôi đã dành một mục riêng cho nó. Tương tự, chúng tôi cũng tách riêng [Thuộc tính sự kiện thuế và doanh thu bổ sung](webhook-event-types-and-fields#additional-tax-and-revenue-event-properties), vì chúng chỉ áp dụng cho một số loại sự kiện nhất định. #### Đối với hầu hết các loại sự kiện \{#for-most-event-types\} Các thuộc tính sự kiện của hầu hết các loại sự kiện đều nhất quán (ngoại trừ sự kiện **Access Level Updated**, được mô tả trong phần riêng của nó). Dưới đây là bảng tổng hợp các thuộc tính và chỉ rõ chúng thuộc về những sự kiện cụ thể nào. | Trường | Kiểu | Mô tả | |:------------------------------|:--------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **ab_test_name** | String | Tên của [A/B test Adapty](ab-tests) nơi giao dịch phát sinh. | | **ab_test_revision** | Integer | Phiên bản của A/B test nơi giao dịch phát sinh. | | **base_plan_id** | String | [Base plan ID](https://support.google.com/googleplay/android-developer/answer/12154973) trong Google Play Store hoặc [price ID](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) trong Stripe. | | **cancellation_reason** | String |Các lý do hủy có thể có: `voluntarily_cancelled`, `billing_error`, `price_increase`, `product_was_not_available`, `refund`, `cancelled_by_developer`, `new_subscription_replace`, `upgraded`, `unknown`, `adapty_revoked`.
Xuất hiện trong các loại sự kiện sau:
`subscription_cancelled`, `subscription_refunded` và `trial_cancelled`. | | **cohort_name** | String | Tên của [đối tượng](audience) đã xác định paywall nào được hiển thị cho người dùng. | | **consecutive_payments** | Integer | Số kỳ mà người dùng đã đăng ký liên tục không gián đoạn. Bao gồm kỳ hiện tại. | | **currency** | String | Đơn vị tiền tệ địa phương. | | **developer_id** | String | ID của [placement](placements) nơi giao dịch phát sinh. | | **environment** | String | Giá trị có thể là `Sandbox` hoặc `Production`. | | **event_datetime** | ISO 8601 date | Ngày và giờ của sự kiện. Giống với giá trị ở cấp gốc của sự kiện. | | **original_purchase_date** | ISO 8601 date | Đối với gói đăng ký định kỳ, giao dịch mua ban đầu là giao dịch đầu tiên trong chuỗi, ID của nó được gọi là original transaction ID, liên kết chuỗi gia hạn; các giao dịch sau là phần mở rộng của nó. Ngày mua ban đầu là ngày và giờ của giao dịch đầu tiên này. | | **original_transaction_id** | String |Đối với gói đăng ký định kỳ, đây là original transaction ID liên kết chuỗi gia hạn. Giao dịch ban đầu là giao dịch đầu tiên trong chuỗi; các giao dịch sau là phần mở rộng của nó.
Nếu không có phần mở rộng, `original_transaction_id` trùng với store_transaction_id.
| | **paywall_name** | String | Tên của paywall nơi giao dịch phát sinh. | | **paywall_revision** | String | Phiên bản của paywall nơi giao dịch phát sinh. Giá trị mặc định là 1. | | **price_local** | Float | Giá sản phẩm trước khi Apple/Google khấu trừ, tính bằng đơn vị tiền tệ địa phương. | | **price_usd** | Float | Giá sản phẩm trước khi Apple/Google khấu trừ, tính bằng USD. | | **profile_country** | String | Được Adapty xác định dựa trên IP của hồ sơ người dùng. | | **profile_event_id** | UUID | ID sự kiện duy nhất có thể dùng để loại trùng lặp. | | **profile_has_access_level** | Boolean | Giá trị boolean cho biết hồ sơ người dùng có mức độ truy cập đang hoạt động hay không. | | **profile_id** | UUID | ID hồ sơ người dùng do Adapty tạo ra. Giống với giá trị ở cấp gốc của sự kiện. | | **profile_ip_address** | String | IP của hồ sơ người dùng (có thể là IPv4 hoặc IPv6, ưu tiên IPv4 khi có). `null` nếu **Collect users' IP addresses** bị tắt trong [cài đặt ứng dụng](https://app.adapty.io/settings/general). | | **profile_total_revenue_usd** | Float | Tổng doanh thu của hồ sơ người dùng sau khi đã trừ các khoản hoàn tiền. | | **promotional_offer_id** | String | ID Adapty của [ưu đãi](offers) đã được áp dụng. Bạn đặt ID này khi tạo ưu đãi trong dashboard. | | **purchase_date** | ISO 8601 date | Ngày và giờ mua sản phẩm. | | **rate_after_first_year** | Boolean | Boolean cho biết gói đăng ký đủ điều kiện áp dụng mức hoa hồng giảm (thường là 15%) sau một năm gia hạn liên tục. Mức hoa hồng thay đổi tùy theo điều kiện tham gia chương trình và quốc gia. Xem [Hoa hồng cửa hàng và thuế](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue) để biết thêm chi tiết. | | **store** | String | Cửa hàng nơi sản phẩm được mua. Giá trị tiêu chuẩn: **app_store**, **play_store**, **stripe**, **paddle**.ID sản phẩm trong Apple App Store, Google Play Store hoặc Stripe.
Nếu quyền truy cập được cấp mà không có giao dịch thực từ cửa hàng, `vendor_product_id` sẽ là một trong các giá trị sau:
Đối với các gói đăng ký định kỳ, đây là original transaction ID liên kết chuỗi gia hạn. Giao dịch ban đầu là giao dịch đầu tiên trong chuỗi; các giao dịch sau là phần mở rộng của nó.
Nếu không có phần mở rộng nào, `original_transaction_id` trùng với store_transaction_id.
Mã định danh giao dịch của lần mua ban đầu. | | **paywall_name** | String | Tên paywall nơi giao dịch bắt nguồn. | | **paywall_revision** | String | Phiên bản của paywall nơi giao dịch bắt nguồn. Giá trị mặc định là 1. | | **profile_country** | String | Được xác định bởi Adapty, dựa trên IP của hồ sơ người dùng. | | **profile_event_id** | UUID | ID sự kiện duy nhất có thể dùng để loại trùng lặp. | | **profile_has_access_level** | Boolean | Boolean cho biết hồ sơ người dùng có mức độ truy cập đang hoạt động hay không. | | **profile_id** | UUID | ID hồ sơ người dùng nội bộ của Adapty. | | **profile_ip_address** | String | IP của hồ sơ người dùng (có thể là IPv4 hoặc IPv6, ưu tiên IPv4 khi có). `null` nếu **Collect users' IP addresses** bị tắt trong [cài đặt ứng dụng](https://app.adapty.io/settings/general). | | **profile_total_revenue_usd** | Float | Tổng doanh thu của hồ sơ người dùng, bao gồm cả hoàn tiền. | | **purchase_date** | ISO 8601 date | Ngày và giờ mua sản phẩm. | | **renewed_at** | ISO 8601 date | Ngày và giờ khi quyền truy cập sẽ được gia hạn. | | **starts_at** | ISO 8601 date | Ngày và giờ khi mức độ truy cập bắt đầu. | | **store** | String | Cửa hàng nơi sản phẩm được mua. Các giá trị tiêu chuẩn: **app_store**, **play_store**, **stripe**, **paddle**.ID sản phẩm trong cửa hàng (Apple/Google/Stripe).
Nếu quyền truy cập được cấp mà không có giao dịch cửa hàng thực, `vendor_product_id` sẽ là một trong các giá trị sau:
1. **Bạn thiết lập endpoint của mình:** 1. Đảm bảo server của bạn có thể xử lý các yêu cầu từ Adapty với header **Content-Type** được đặt thành `application/json`. 2. Cấu hình server để nhận yêu cầu xác minh từ Adapty và phản hồi với bất kỳ trạng thái `2xx` nào kèm theo nội dung JSON. 3. [Xử lý các sự kiện gói đăng ký](#subscription-events) sau khi kết nối được xác minh. 2. **Bạn cấu hình và bật tích hợp webhook** trong [Adapty Dashboard](#configure-webhook-integration-in-the-adapty-dashboard). Bạn cũng có thể [ánh xạ các sự kiện Adapty sang tên sự kiện tùy chỉnh](#configure-webhook-integration-in-the-adapty-dashboard). Chúng tôi khuyến nghị kiểm tra trong môi trường **Sandbox** trước khi chuyển sang môi trường production. 3. **Adapty gửi yêu cầu xác minh** đến server của bạn. 4. **Server của bạn phản hồi** với trạng thái `2XX` và nội dung JSON. 5. **Sau khi Adapty nhận được phản hồi hợp lệ, hệ thống bắt đầu gửi các sự kiện gói đăng ký.** ## Thiết lập server để xử lý yêu cầu từ Adapty \{#set-up-your-server-to-process-adapty-requests\} Adapty sẽ gửi đến endpoint webhook của bạn 2 loại yêu cầu: 1. [Yêu cầu xác minh](#verification-request): yêu cầu ban đầu để xác minh kết nối đã được thiết lập đúng cách. Yêu cầu này sẽ không chứa bất kỳ sự kiện nào và sẽ được gửi ngay khi bạn nhấn nút **Save** trong phần tích hợp Webhook của Adapty Dashboard. Để xác nhận endpoint của bạn đã nhận thành công yêu cầu xác minh, endpoint cần phản hồi với thông báo xác minh. 2. [Sự kiện gói đăng ký](#subscription-events): yêu cầu tiêu chuẩn mà server Adapty gửi mỗi khi có sự kiện được tạo. Server của bạn không cần phản hồi với bất kỳ nội dung cụ thể nào. Điều duy nhất server Adapty cần là nhận được phản hồi HTTP mã 200 tiêu chuẩn khi nhận tin nhắn thành công. ### Yêu cầu xác minh \{#verification-request\} Sau khi bạn bật tích hợp webhook trong Adapty Dashboard, Adapty sẽ gửi một yêu cầu POST xác minh chứa một đối tượng JSON rỗng `{}` làm nội dung. Thiết lập endpoint của bạn với **Content-Type header** là `application/json`, tức là endpoint server của bạn cần được cấu hình để nhận các yêu cầu webhook với payload định dạng JSON. Server của bạn phải phản hồi với mã trạng thái 2xx và gửi bất kỳ phản hồi JSON hợp lệ nào, ví dụ: ```json title="Json" {} ``` Sau khi Adapty nhận được phản hồi xác minh đúng định dạng với mã trạng thái 2xx, tích hợp webhook Adapty của bạn đã được cấu hình hoàn chỉnh. ### Sự kiện gói đăng ký \{#subscription-events\} Các sự kiện gói đăng ký được gửi với header **Content-Type** được đặt thành `application/json` và chứa dữ liệu sự kiện ở định dạng JSON. Để biết các loại sự kiện và cấu trúc yêu cầu, xem [Loại và trường sự kiện Webhook](webhook-event-types-and-fields). ## Cấu hình tích hợp webhook trong Adapty Dashboard \{#configure-webhook-integration-in-the-adapty-dashboard\} Trong Adapty, bạn có thể cấu hình các flow riêng biệt cho sự kiện production và sự kiện kiểm thử nhận từ môi trường sandbox của Apple, Stripe hoặc tài khoản thử nghiệm Google. :::tip Adapty hỗ trợ một URL webhook cho mỗi môi trường (production và sandbox). Để chuyển sự kiện đến nhiều dịch vụ, hãy trỏ webhook vào backend của bạn rồi phân phối từ đó. ::: Đối với sự kiện production, sử dụng trường **Production endpoint URL** để chỉ định URL mà các callback sẽ được gửi đến. Ngoài ra, hãy cấu hình trường **Authorization header value for production endpoint** — header này giúp server của bạn xác thực các sự kiện từ Adapty. Lưu ý rằng chúng tôi sẽ sử dụng giá trị được chỉ định trong trường **Authorization header value for production endpoint** làm header `Authorization` chính xác như đã nhập, không có bất kỳ thay đổi hay bổ sung nào. Đối với sự kiện kiểm thử, hãy sử dụng các trường **Sandbox endpoint URL** và **Authorization header value for sandbox endpoint** tương ứng. Để thiết lập tích hợp webhook: 1. Mở [Integrations -> Webhook](https://app.adapty.io/integrations/customwebhook) trong Adapty Dashboard của bạn.
2. Bật toggle để khởi động tích hợp.
4. Điền vào các trường tích hợp:
| Trường | Mô tả |
| ------------------------------------------------------ | ------------------------------------------------------------ |
| **Production endpoint URL** | URL mà Adapty dùng để gửi các yêu cầu HTTP POST cho sự kiện trong môi trường production. |
| **Authorization header value for production endpoint** | Header mà server của bạn sẽ dùng để xác thực các yêu cầu từ Adapty trong môi trường production. Lưu ý rằng chúng tôi sẽ sử dụng giá trị được chỉ định trong trường này làm header `Authorization` chính xác như đã nhập, không có bất kỳ thay đổi hay bổ sung nào.
Mặc dù không bắt buộc, nhưng rất khuyến khích thiết lập để tăng cường bảo mật.
| Ngoài ra, để phục vụ nhu cầu kiểm thử trong môi trường sandbox, có thêm hai trường khác: | Trường kiểm thử | Mô tả | | --------------------------------------------------- | ------------------------------------------------------------ | | **Sandbox endpoint URL** | URL mà Adapty dùng để gửi các yêu cầu HTTP POST cho sự kiện trong môi trường sandbox. | | **Authorization header value for sandbox endpoint** |Header mà server của bạn sẽ dùng để xác thực các yêu cầu từ Adapty trong quá trình kiểm thử ở môi trường sandbox. Lưu ý rằng chúng tôi sẽ sử dụng giá trị được chỉ định trong trường này làm header `Authorization` chính xác như đã nhập, không có bất kỳ thay đổi hay bổ sung nào.
Mặc dù không bắt buộc, nhưng rất khuyến khích thiết lập để tăng cường bảo mật.
| 4. (Tùy chọn) Chọn các sự kiện bạn muốn nhận và ánh xạ tên của chúng. Xem [Event flows](event-flows) để biết những sự kiện nào được kích hoạt trong các tình huống khác nhau. Nếu ID sự kiện của bạn khác với ID được dùng trong Adapty, hãy giữ nguyên ID trong hệ thống của bạn và thay thế các ID sự kiện mặc định của Adapty bằng ID của bạn trong phần **Events names** của trang [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). ID sự kiện có thể là bất kỳ chuỗi nào; chỉ cần đảm bảo ID sự kiện trong server xử lý webhook của bạn trùng khớp với ID bạn đã nhập trong Adapty Dashboard. Bạn không thể để trống ID sự kiện cho các sự kiện đã được bật.
5. Các trường và tùy chọn bổ sung không bắt buộc; hãy sử dụng khi cần:
| Cài đặt | Mô tả |
| :--------------------------------- | :----------------------------------------------------------- |
| **Send Trial Price** | Khi bật, Adapty sẽ bao gồm giá gói đăng ký trong các trường `price_local` và `price_usd` cho sự kiện **Trial Started**. |
| **Exclude Historical Events** | Chọn để loại trừ các sự kiện xảy ra trước khi người dùng cài đặt ứng dụng có tích hợp Adapty SDK. Điều này giúp tránh trùng lặp sự kiện và đảm bảo báo cáo chính xác. Ví dụ: nếu người dùng kích hoạt gói đăng ký hàng tháng vào ngày 10 tháng 1 và cập nhật ứng dụng với Adapty SDK vào ngày 6 tháng 3, Adapty sẽ bỏ qua các sự kiện trước ngày 6 tháng 3 và giữ lại các sự kiện sau đó. |
| **Send user attributes** | Bật tùy chọn này để gửi các thuộc tính dành riêng cho người dùng, chẳng hạn như tùy chọn ngôn ngữ. Các thuộc tính này sẽ xuất hiện trong trường `user_attributes`. Xem [Trường sự kiện](webhook-event-types-and-fields#event-fields) để biết thêm thông tin. |
| **Send attribution** | Bật tùy chọn này để bao gồm thông tin attribution (ví dụ: dữ liệu AppsFlyer) trong trường `attributions`. Tham khảo phần [Dữ liệu Attribution](webhook-event-types-and-fields#attributions) để biết chi tiết. |
| **Send Play Store purchase token** | Bật tùy chọn này để nhận token Play Store cần thiết cho việc xác thực lại giao dịch mua, nếu cần. Khi bật, tham số `play_store_purchase_token` sẽ được thêm vào sự kiện. Để biết chi tiết về nội dung của nó, tham khảo phần [Play Store purchase token](webhook-event-types-and-fields#play-store-purchase-token). |
6. Nhớ nhấn nút **Save** để xác nhận các thay đổi.
Ngay khi bạn nhấn nút **Save**, Adapty sẽ gửi yêu cầu xác minh và chờ phản hồi xác minh từ server của bạn.
### Chọn sự kiện cần gửi và ánh xạ tên sự kiện \{#choose-events-to-send-and-map-event-names\}
Chọn các sự kiện bạn muốn nhận trên server bằng cách bật toggle bên cạnh sự kiện đó. Nếu tên sự kiện của bạn khác với tên được dùng trong Adapty và bạn cần giữ nguyên tên của mình, bạn có thể thiết lập ánh xạ bằng cách thay thế tên sự kiện mặc định của Adapty bằng tên của bạn trong phần **Events names** của trang [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook).
Tên sự kiện có thể là bất kỳ chuỗi nào. Bạn không thể để trống các trường cho sự kiện đã được bật. Nếu bạn vô tình xóa tên sự kiện Adapty, bạn luôn có thể sao chép tên từ chủ đề [Sự kiện gửi đến tích hợp bên thứ ba](events).
## Xử lý sự kiện webhook \{#handle-webhook-events\}
Webhook thường được gửi trong vòng 5 đến 60 giây sau khi sự kiện xảy ra. Tuy nhiên, các sự kiện hủy có thể mất đến 2 giờ để được gửi sau khi người dùng hủy gói đăng ký của họ.
Nếu mã trạng thái phản hồi của server nằm ngoài khoảng 200-404, Adapty sẽ thử gửi lại với backoff theo cấp số nhân. Lần thử lại đầu tiên xảy ra khoảng **1 phút** sau lần thất bại đầu tiên, tăng gấp đôi sau mỗi lần tiếp theo — tối đa 9 lần thử trong vòng 24 giờ. Chúng tôi khuyến nghị bạn thiết lập webhook chỉ thực hiện các xác thực cơ bản đối với nội dung sự kiện từ Adapty trước khi phản hồi. Nếu server của bạn không thể xử lý sự kiện và bạn không muốn Adapty thử lại, hãy sử dụng mã trạng thái trong khoảng 200-404. Ngoài ra, hãy xử lý các tác vụ tốn thời gian một cách bất đồng bộ và phản hồi Adapty nhanh chóng. Nếu Adapty không nhận được phản hồi trong vòng 10 giây, hệ thống sẽ coi đó là lần thất bại và sẽ thử lại.
---
# File: test-webhook
---
---
title: "Kiểm tra tích hợp webhook"
description: "Kiểm tra tích hợp webhook trong Adapty để tự động theo dõi sự kiện gói đăng ký."
---
Sau khi thiết lập xong tích hợp, đã đến lúc kiểm tra. Bạn có thể kiểm tra cả tích hợp sandbox lẫn production. Chúng tôi khuyến nghị bắt đầu với môi trường sandbox và xác thực tối đa trên đó:
- Các sự kiện được gửi đi và chuyển phát thành công.
- Bạn đã thiết lập đúng các tùy chọn cho sự kiện lịch sử, giá gói đăng ký cho sự kiện **Trial started**, attribution, thuộc tính người dùng, và token mua hàng Google Play Store để gửi hoặc không gửi kèm theo sự kiện.
- Bạn đã ánh xạ tên sự kiện đúng và server của bạn có thể xử lý chúng.
## Cách kiểm tra \{#how-to-test\}
Trước khi bắt đầu kiểm tra tích hợp, hãy đảm bảo bạn đã:
1. Thiết lập tích hợp webhook như mô tả trong phần [Thiết lập tích hợp webhook](set-up-webhook-integration).
2. Thiết lập môi trường như mô tả trong các phần [Kiểm tra in-app purchase trên Apple App Store](test-purchases-in-sandbox) và [Kiểm tra in-app purchase trên Google Play Store](testing-on-android). Đảm bảo bạn đã build ứng dụng thử nghiệm trong môi trường sandbox chứ không phải production.
3. Thực hiện mua hàng/bắt đầu dùng thử/hoàn tiền để tạo ra sự kiện bạn đã chọn gửi đến webhook. Ví dụ, để nhận sự kiện **Subscription started**, hãy mua một gói đăng ký mới.
## Xác thực kết quả \{#validation-of-the-result\}
### Kết quả gửi sự kiện thành công \{#successful-sending-events-result\}
Khi tích hợp thành công, một sự kiện sẽ xuất hiện trong phần **Last sent events** của tích hợp và có trạng thái **Success**.
### Kết quả gửi sự kiện thất bại \{#unsuccessful-sending-events-result\}
| Vấn đề | Giải pháp |
|-----|--------|
| Sự kiện không xuất hiện | Giao dịch mua hàng của bạn chưa xảy ra và do đó sự kiện không được tạo ra. Tham khảo phần [Khắc phục sự cố mua hàng thử nghiệm](troubleshooting-test-purchases) để tìm giải pháp. |
| Sự kiện xuất hiện và có trạng thái **Sending failed** | Chúng tôi xác định khả năng chuyển phát dựa trên HTTP status và coi mọi giá trị **ngoài phạm vi 200-399** là thất bại.
Để tìm hiểu thêm về vấn đề, hãy di chuột qua trạng thái **Sending failed** của sự kiện thất bại như hiển thị bên dưới.
|
---
# File: handle-integration-errors
---
---
title: "Xử lý lỗi trong tích hợp"
description: "Xử lý lỗi trong tích hợp"
---
Khi sử dụng bất kỳ tích hợp attribution, messaging hay analytics nào, bạn có thể gặp một số lỗi phổ biến. Xem hướng dẫn này để biết cách xử lý các trường hợp đó.
## Sai lệch dữ liệu \{#data-discrepancy\}
**Nguyên nhân**: Điều này có thể xảy ra vì không phải tất cả người dùng đều đang dùng phiên bản app có tích hợp Adapty SDK.
**Giải pháp**: Để đảm bảo tính nhất quán của dữ liệu, bạn có thể yêu cầu người dùng cập nhật app lên phiên bản có Adapty SDK.
## Lỗi mạng \{#network-errors\}
**Nguyên nhân**: Nhiều khả năng là do mất kết nối internet giữa server Adapty và server tích hợp.
**Giải pháp**: Những sự cố này thường không kéo dài và chỉ ảnh hưởng đến một số ít sự kiện.
## Server tích hợp không xử lý được sự kiện \{#integration-server-failed-to-process-the-event\}
**Nguyên nhân**: Tích hợp được thiết lập không đúng cách.
**Giải pháp**: Xem bài viết về tích hợp trong tài liệu của chúng tôi. Đảm bảo bạn đã hoàn tất tất cả các bước thiết lập trên Adapty Dashboard, phía công cụ bên thứ ba, và trong code của app.
## Thiếu dữ liệu tích hợp \{#missing-integration-data\}
**Nguyên nhân**: Hồ sơ người dùng thiếu một số ID đặc thù cho tích hợp. Điều này có thể xảy ra khi tích hợp chưa được thiết lập đúng cách trong code của app.
**Giải pháp**: Xem bài viết về tích hợp trong tài liệu của chúng tôi. Đảm bảo bạn đã triển khai các phương thức từ đoạn code mẫu trong app, và các phương thức này thực sự tương tác với hồ sơ người dùng của bạn.
## Thiếu thông tin xác thực tích hợp \{#missing-integration-credentials\}
**Nguyên nhân**: Một số thông tin xác thực của tích hợp bị thiếu hoặc không chính xác.
**Giải pháp**: Vui lòng kiểm tra lại tất cả thông tin xác thực cho tích hợp đó trên Adapty Dashboard. Sự cố có thể xảy ra do không khớp về phiên bản hoặc môi trường.
## Sự kiện đã hết hạn \{#the-event-has-expired\}
**Nguyên nhân**: Tùy chọn **Exclude historical events** được bật trong cài đặt tích hợp, và ngày tạo sự kiện xảy ra trước ngày tạo hồ sơ người dùng trong hệ thống của chúng tôi.
Điều này có thể xảy ra khi một chuỗi giao dịch bắt đầu từ nhiều năm trước được đưa vào Adapty thông qua xác thực biên lai cho một hồ sơ người dùng mới tạo gần đây.
**Giải pháp**: Đảm bảo điều này không xảy ra với các sự kiện mới. Nếu bạn muốn gửi các sự kiện lịch sử đến tích hợp, hãy tắt **Exclude historical events**.
## Loại sự kiện bị vô hiệu hóa/không được hỗ trợ \{#disabledunsupported-event-type\}
**Nguyên nhân**: Sự kiện không được hỗ trợ cho tích hợp này, hoặc bạn đã tắt nó khi thiết lập tích hợp. Ví dụ, các sự kiện `access_level_updated` không được hỗ trợ bởi hầu hết các tích hợp.
**Giải pháp**: Kiểm tra trong tài liệu tích hợp xem tích hợp có hỗ trợ loại sự kiện này không. Nếu có, trên Adapty Dashboard, hãy đảm bảo rằng loại sự kiện này đã được bật trong cài đặt tích hợp.
---
# File: manage-adapty-with-ai
---
---
title: "Manage Adapty with AI agents and coding tools"
description: "Every way to use Adapty with AI — integrate the SDK with a coding agent, pull analytics with an LLM, and feed Adapty docs to your AI tool."
---
Adapty works with AI coding tools and agents. Use them to integrate the SDK, ask about your analytics, or look up Adapty docs without leaving your editor. This page lists what's available and who each tool is for.
## Integrate the Adapty SDK with AI
Two ways to add the Adapty SDK to your app with an AI coding tool. Both work with Cursor, Claude, and other AI assistants.
### Skill-based integration
The Adapty SDK integration skill runs the whole integration from your AI coding tool in one command. Use it when you want a guided, automated setup.
Choose your platform: [iOS](adapty-sdk-integration-skill) · [Android](adapty-sdk-integration-skill-android) · [React Native](adapty-sdk-integration-skill-react-native) · [Flutter](adapty-sdk-integration-skill-flutter) · [Unity](adapty-sdk-integration-skill-unity) · [Kotlin Multiplatform](adapty-sdk-integration-skill-kmp) · [Capacitor](adapty-sdk-integration-skill-capacitor)
### Step-by-step integration
Walk your AI tool through the integration stage by stage, feeding it the right docs in order. Use it when you want to review each step as you go.
Choose your platform: [iOS](adapty-cursor) · [Android](adapty-cursor-android) · [React Native](adapty-cursor-react-native) · [Flutter](adapty-cursor-flutter) · [Unity](adapty-cursor-unity) · [Kotlin Multiplatform](adapty-cursor-kmp) · [Capacitor](adapty-cursor-capacitor)
## Manage Adapty from the command line
The [Adapty Developer CLI](developer-cli-quickstart) lets you manage your Adapty entities — apps, access levels, products, paywalls, and placements — from the terminal, without opening the dashboard. Because it's a command-line tool, your AI coding agent can run it directly.
## Ask about your data
Point an AI coding agent at the Export Analytics API to query your metrics in plain language — revenue, retention, LTV, and more. No MCP server required.
[Ask AI about your analytics data](export-analytics-with-ai)
## Give your AI tool the Adapty docs
### Plain-text docs
Every Adapty doc is available as Markdown — add `.md` to the page URL, or click **Copy for LLM** under the title. For broader context, give your tool the [`llms.txt`](https://adapty.io/docs/llms.txt) index or a platform-specific subset such as [`ios-llms.txt`](https://adapty.io/docs/ios-llms.txt).
### Context7
[Context7](https://context7.com/adaptyteam/adapty-docs) is an MCP server that serves Adapty docs to your AI tool, but it indexes only code snippets — not the full prose. Use it for quick code examples; for complete guidance, give your tool the plain-text docs above. Context7 works with Cursor, Claude Code, Windsurf, and other MCP-compatible tools.
---
# File: export-analytics-with-ai
---
---
title: "Hỏi AI về dữ liệu analytics của bạn"
description: "Truy vấn analytics Adapty bằng ngôn ngữ tự nhiên với AI coding agent, sử dụng Export Analytics API."
---
Hỏi AI coding agent về dữ liệu analytics Adapty của bạn bằng ngôn ngữ tự nhiên — doanh thu, conversion, retention, LTV — và để nó kéo số liệu cho bạn. Trỏ một công cụ có thể thực hiện API call vào [Export Analytics API](https://adapty.io/docs/vi/export-analytics-api.md), và nó sẽ truy vấn các chỉ số của bạn theo yêu cầu.
## Bạn có thể hỏi về những gì \{#what-you-can-ask-about\}
Export Analytics API trả về các chỉ số giống như những gì bạn thấy trong các biểu đồ trên Adapty dashboard. Mỗi chỉ số có một operation riêng:
| Chỉ số | Phạm vi | Operation |
| --- | --- | --- |
| Revenue, MRR, ARR, ARPU | Tiền kiếm được theo thời gian, phân nhóm theo kỳ, quốc gia hoặc chiến dịch | [retrieveAnalyticsData](https://adapty.io/docs/vi/api-export-analytics/operations/retrieveAnalyticsData.md) |
| Cohort retention | Thời gian người dùng trong một cohort nhất định tiếp tục thanh toán | [retrieveCohortData](https://adapty.io/docs/vi/api-export-analytics/operations/retrieveCohortData.md) |
| Tỷ lệ conversion | Số lượng người dùng chuyển từ bước này sang bước khác hoặc từ kênh này sang kênh khác | [retrieveConversionData](https://adapty.io/docs/vi/api-export-analytics/operations/retrieveConversionData.md) |
| Churn và funnel | Nơi người dùng rời bỏ và tốc độ hủy đăng ký | [retrieveFunnelData](https://adapty.io/docs/vi/api-export-analytics/operations/retrieveFunnelData.md) |
| Lifetime value (LTV) | Doanh thu trung bình mỗi phân khúc người dùng theo thời gian | [retrieveLTVData](https://adapty.io/docs/vi/api-export-analytics/operations/retrieveLTVData.md) |
| Retention | Tỷ lệ người dùng vẫn còn hoạt động sau một số ngày nhất định | [retrieveRetentionData](https://adapty.io/docs/vi/api-export-analytics/operations/retrieveRetentionData.md) |
Để xem đầy đủ danh sách các tham số và bộ lọc, xem [tài liệu tham khảo API](https://adapty.io/docs/vi/api-export-analytics.md).
## Trước khi bắt đầu \{#before-you-start\}
Bạn cần ba thứ:
- **Tài khoản Adapty có dữ liệu**: API trả về các chỉ số giống như trong biểu đồ dashboard, vì vậy ứng dụng của bạn phải đã thu thập analytics.
- **Secret API key**: Tìm tại [App settings → General](https://app.adapty.io/settings/general), trong trường **Secret key**. Key gắn với từng app, vì vậy hãy dùng key riêng cho mỗi app. Lưu key trong biến môi trường (ví dụ: `ADAPTY_SECRET_KEY`) để agent đọc được mà không cần bạn dán vào chat.
- **Công cụ AI có thể gọi API**: Ví dụ: Claude Code, Cursor, hoặc Claude Desktop với fetch tool. Các công cụ chat thông thường như claude.ai hay ChatGPT không thể gọi API trực tiếp.
## Cung cấp API spec cho agent \{#give-your-agent-the-api-spec\}
[OpenAPI spec](https://adapty.io/docs/vi/api-specs/export-analytics-api.yaml) mô tả mọi endpoint, header xác thực, request body và các response mẫu. Khi agent có spec, nó sẽ tạo request chính xác mà không cần bạn viết code.
Cung cấp spec cho agent qua URL:
- **Dán URL**: Nếu agent có thể fetch URL, cung cấp `https://adapty.io/docs/vi/api-specs/export-analytics-api.yaml` và yêu cầu nó đọc spec.
- **Dùng fetch tool**: Nếu agent có tool để lấy URL (ví dụ: MCP fetch server), trỏ nó vào cùng URL đó.
Spec đặt base URL là `https://api-admin.adapty.io`, vì vậy agent có đủ mọi thứ cần thiết khi key của bạn đã có trong môi trường.
## Hỏi về dữ liệu của bạn \{#ask-about-your-data\}
Khi đã load spec và lưu key vào biến môi trường, hãy mô tả chỉ số bạn muốn bằng ngôn ngữ tự nhiên.
Ví dụ các câu hỏi:
```
What was my MRR at the end of each month this year, and how does it compare to last year?
Show my trial-to-paid conversion rate for the last 90 days, broken down by product.
Which countries drive the most revenue from my yearly subscription? Top 10.
How is week-1 retention trending for subscribers who started in the last 6 months?
What's the refund rate on my annual plan since launch, by month?
Compare LTV for paid-campaign users vs. organic over the last year, and export it as CSV.
```
Agent sẽ ánh xạ yêu cầu của bạn vào operation phù hợp, đọc key từ môi trường và trả về dữ liệu. Response mặc định là JSON. Yêu cầu CSV khi bạn muốn file có thể dùng trong bảng tính — agent sẽ đặt `format` thành `csv` trong request body.
:::warning
Lưu secret key trong biến môi trường — không dán vào chat hay commit vào file rules. Key gắn với từng app, vì vậy hãy xoay vòng key trong **Settings → General** nếu bị lộ. Xem [xoay vòng API key](https://adapty.io/docs/vi/export-analytics-api-authorization.md).
:::
## Thiết lập một lần để dùng lại \{#set-up-once-for-repeated-use\}
Để tránh lặp lại thiết lập mỗi phiên làm việc, hãy lưu spec và key để agent có thể tái sử dụng:
- **Lưu link spec**: Thêm URL spec vào file rules hoặc memory của agent (ví dụ: file `CLAUDE.md` hoặc Cursor rules file) để nó load mỗi phiên.
- **Lưu key trong môi trường**: Giữ `ADAPTY_SECRET_KEY` trong shell profile hoặc secret store của công cụ, để bạn không bao giờ phải dán lại.
- **Lưu các prompt hay tạo custom skill**: Giữ các câu hỏi thường dùng dưới dạng saved prompt, hoặc bọc chúng trong custom skill hay slash command để agent chạy báo cáo theo yêu cầu.
## Giới hạn \{#limits\}
Hãy lưu ý những ràng buộc sau:
- **Giới hạn tốc độ**: API cho phép 2 request mỗi giây mỗi API key. Vượt quá sẽ trả về lỗi `429 Too Many Requests`. Hãy bảo agent chờ và thử lại khi gặp `429`.
- **Key theo từng app**: Mỗi key chỉ hoạt động cho một app. Để lấy dữ liệu từ nhiều app, cung cấp key tương ứng cho từng app.
- **Định dạng đầu ra**: Response mặc định là JSON. Đặt `format` thành `csv` trong request body để xuất CSV.
Để xem đầy đủ quy tắc xác thực và request, xem [Authorization và định dạng request](https://adapty.io/docs/vi/export-analytics-api-authorization.md).
---
# File: handle-webhooks-with-ai
---
---
title: "Xử lý sự kiện gói đăng ký Adapty bằng webhook"
description: "Nhận và xử lý sự kiện gói đăng ký Adapty trên máy chủ của bạn bằng webhook — thiết lập endpoint, xác thực, payload và kiểm thử trên một trang."
---
Webhook cho phép máy chủ của bạn nhận sự kiện gói đăng ký Adapty theo thời gian thực — mua hàng, gia hạn, hủy, sự cố thanh toán và hoàn tiền — để bạn có thể cấp quyền truy cập, đồng bộ backend, hoặc kích hoạt các quy trình tự động. Hướng dẫn này đưa bạn từ bước cài đặt endpoint đến một tích hợp đã được xác minh và kiểm thử, đồng thời hướng dẫn cách để AI coding agent viết handler cho stack của bạn.
:::tip
Đang dùng AI coding agent? Nhấn **Copy for LLM** bên dưới tiêu đề và dán toàn bộ trang này vào agent — nó có đủ thông tin về thiết lập, payload và logic handler cần thiết.
:::
## Webhook Adapty hoạt động như thế nào \{#how-adapty-webhooks-work\}
- **Một chiều và thời gian thực**: Adapty gửi HTTP `POST` đến máy chủ của bạn khi có sự kiện xảy ra — không cần polling.
- **Hai loại request**: Một request xác minh (gửi một lần khi bạn lưu tích hợp) và các sự kiện gói đăng ký liên tục.
- **Một URL cho mỗi môi trường**: Bạn cài đặt endpoint riêng cho môi trường production và sandbox.
- **Bạn phải xác nhận từng request**: Phản hồi với trạng thái `2xx` nhanh chóng, và Adapty sẽ thử lại nếu thất bại.
## Xây dựng endpoint của bạn \{#build-your-endpoint\}
Tạo một HTTPS endpoint công khai xử lý hai loại request:
- **Request xác minh**: Gửi một lần khi bạn lưu tích hợp. Nó có body JSON rỗng (`{}`). Phản hồi với trạng thái `2xx` và một body JSON.
- **Sự kiện gói đăng ký**: Các request `POST` liên tục với sự kiện trong body. Phản hồi `200` trong vòng 10 giây, sau đó thực hiện các tác vụ nặng theo cách bất đồng bộ.
Chọn một chuỗi bí mật và lưu nó làm biến môi trường (ví dụ: `ADAPTY_WEBHOOK_SECRET`). Với mỗi request, hãy xác minh header `Authorization` khớp với nó, và từ chối request nếu không khớp — bạn sẽ nhập cùng bí mật này vào dashboard sau.
```javascript title="webhook.js"
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.ADAPTY_WEBHOOK_SECRET;
app.post("/adapty/webhook", (req, res) => {
// 1. Verify the shared secret Adapty echoes back.
if (req.get("Authorization") !== WEBHOOK_SECRET) {
return res.sendStatus(401);
}
// 2. Acknowledge fast, then process asynchronously.
res.status(200).json({});
// 3. The verification request has an empty body — nothing to handle.
const event = req.body;
if (!event.event_type) return;
switch (event.event_type) {
case "subscription_started":
case "subscription_renewed":
case "trial_converted":
// Grant or extend access.
break;
case "subscription_expired":
case "subscription_refunded":
// Revoke access.
break;
default:
break;
}
});
app.listen(3000);
```
Triển khai endpoint lên một HTTPS URL công khai trước khi cài đặt tích hợp — Adapty gửi request xác minh ngay khi bạn lưu.
### Các sự kiện chính và payload \{#key-events-and-the-payload\}
Mỗi sự kiện dùng chung cùng một cấu trúc bao ngoài. Các trường thay đổi tùy theo loại sự kiện, cửa hàng và các tùy chọn bạn đã bật. Dưới đây là sự kiện `subscription_started` đã được rút gọn:
```json title="Example event"
{
"profile_id": "00000000-0000-0000-0000-000000000000",
"customer_user_id": "UserIdInYourSystem",
"event_type": "subscription_started",
"event_datetime": "2024-11-15T10:45:36.181000+0000",
"event_properties": {
"store": "play_store",
"currency": "USD",
"price_usd": 4.99,
"vendor_product_id": "onemonth_no_trial",
"transaction_id": "0000000000000000",
"original_transaction_id": "0000000000000000",
"subscription_expires_at": "2024-12-15T10:45:36.181000+0000",
"profile_event_id": "00000000-0000-0000-0000-000000000000"
},
"event_api_version": 1
}
```
Các sự kiện bạn sẽ xử lý thường xuyên nhất:
| Loại sự kiện | Kích hoạt khi |
| --- | --- |
| `subscription_started` | Người dùng bắt đầu gói đăng ký trả phí |
| `subscription_renewed` | Gói đăng ký gia hạn và thanh toán thành công |
| `subscription_renewal_cancelled` | Người dùng tắt tự động gia hạn (quyền truy cập duy trì đến khi hết hạn) |
| `subscription_expired` | Quyền truy cập kết thúc sau khi gói đăng ký không được gia hạn |
| `trial_started` | Người dùng bắt đầu dùng thử miễn phí |
| `trial_converted` | Bản dùng thử chuyển sang gói đăng ký trả phí |
| `billing_issue_detected` | Thanh toán gia hạn thất bại |
| `subscription_refunded` | Giao dịch mua gói đăng ký được hoàn tiền |
Để xem danh sách sự kiện đầy đủ và tất cả các trường, hãy xem [Loại sự kiện và trường webhook](https://adapty.io/docs/vi/webhook-event-types-and-fields.md).
:::warning
Đừng sắp xếp thứ tự sự kiện theo `event_datetime` — đây là thời gian nghiệp vụ của sự kiện, vì vậy các sự kiện có thể đến không theo thứ tự hoặc trùng timestamp. Hãy sắp xếp theo thời gian bạn nhận được, và loại trùng bằng `profile_event_id` hoặc các transaction ID.
:::
## Cài đặt webhook trong Adapty \{#configure-the-webhook-in-adapty\}
1. Mở [Integrations → Webhook](https://app.adapty.io/integrations/customwebhook) trong Adapty Dashboard.
2. Bật tích hợp.
3. Trong **Production endpoint URL**, nhập HTTPS URL của endpoint bạn đã triển khai.
4. Trong **Authorization header value for production endpoint**, nhập bí mật mà endpoint của bạn kiểm tra. Adapty gửi giá trị này trong header `Authorization` với mỗi request. Tùy chọn nhưng được khuyến nghị mạnh mẽ.
5. Để kiểm thử trong sandbox trước, hãy điền **Sandbox endpoint URL** và **Authorization header value** tương ứng.
6. Nhấn **Save**. Adapty ngay lập tức gửi request xác minh đến endpoint của bạn, endpoint phản hồi với `2xx` để hoàn tất thiết lập.
Để chọn sự kiện cần gửi, ánh xạ tên sự kiện, hoặc bật các trường tùy chọn (giá dùng thử, sự kiện lịch sử, attribution, thuộc tính người dùng, Play Store token), hãy xem [Thiết lập tích hợp webhook](https://adapty.io/docs/vi/set-up-webhook-integration.md).
## Xây dựng với AI coding agent của bạn \{#build-it-with-your-ai-coding-agent\}
Cung cấp cho AI coding agent của bạn hướng dẫn này và tài liệu tham khảo dưới dạng Markdown (thêm `.md` vào bất kỳ URL trang nào), cho nó biết stack của bạn và để nó tạo scaffolding cho handler:
- [Loại sự kiện và trường webhook](https://adapty.io/docs/vi/webhook-event-types-and-fields.md)
- [Thiết lập tích hợp webhook](https://adapty.io/docs/vi/set-up-webhook-integration.md)
Ví dụ prompt:
```
Read these Adapty webhook docs, then write a webhook handler for my Express app:
verify the Authorization header against ADAPTY_WEBHOOK_SECRET, answer the
verification request, acknowledge events with 200, and grant or revoke access
based on event_type.
```
Agent sẽ viết code handler, nhưng nó không thể triển khai endpoint hay cài đặt dashboard — hãy tự host endpoint và đặt URL cùng bí mật trong **Integrations → Webhook**.
## Kiểm thử webhook của bạn \{#test-your-webhook\}
Kiểm thử trong sandbox trước khi đưa lên production:
1. Thiết lập sandbox endpoint và bí mật như mô tả ở trên.
2. Trong app sandbox của bạn, thực hiện mua hàng, bắt đầu dùng thử, hoặc hoàn tiền để kích hoạt sự kiện.
3. Mở phần **Last sent events** của tích hợp. Sự kiện được gửi thành công sẽ hiển thị trạng thái **Success**.
Nếu sự kiện hiển thị **Sending failed**, máy chủ của bạn đã trả về trạng thái ngoài phạm vi 200–399 — di chuột qua trạng thái để xem chi tiết. Để xem toàn bộ quy trình kiểm thử, hãy xem [Kiểm thử tích hợp webhook](https://adapty.io/docs/vi/test-webhook.md).
## Giới hạn \{#limits\}
- **Xác nhận trong vòng 10 giây**: Nếu Adapty không nhận được phản hồi kịp thời, nó coi lần thử đó là thất bại và thử lại.
- **Thử lại**: Nếu trạng thái của bạn nằm ngoài phạm vi 200–404, Adapty thử lại với backoff theo cấp số nhân — tối đa 9 lần trong 24 giờ.
- **Độ trễ hủy**: Sự kiện hủy có thể mất đến 2 giờ để đến.
- **Một URL cho mỗi môi trường**: Để gửi sự kiện đến nhiều dịch vụ, hãy trỏ webhook đến backend của bạn và phân phối từ đó.
---
# File: server-side-api-with-ai
---
---
title: "Kiểm tra và cấp quyền truy cập gói đăng ký từ backend"
description: "Sử dụng API phía máy chủ của Adapty để kiểm tra xem người dùng có gói đăng ký đang hoạt động hay không và cấp quyền truy cập thủ công, với sự hỗ trợ của AI coding agent."
---
Từ backend của bạn, hãy sử dụng API phía máy chủ của Adapty để kiểm tra xem người dùng có gói đăng ký đang hoạt động hay không và cấp quyền truy cập thủ công. Hướng dẫn này đề cập đến hai lời gọi phổ biến nhất — `getProfile` và `grantAccessLevel` — và hướng dẫn cách để AI coding agent viết phần tích hợp cho stack của bạn.
:::tip
Đang dùng AI coding agent? Nhấn **Copy for LLM** bên dưới tiêu đề và dán toàn bộ trang này vào agent — nó có đủ các lời gọi, trường dữ liệu, và những điểm cần lưu ý.
:::
## Trước khi bắt đầu \{#before-you-start\}
- **Secret API key**: Tìm trong [App settings → General](https://app.adapty.io/settings/general), ở trường **Secret key**. Key gắn với từng app cụ thể. Lưu vào biến môi trường (ví dụ: `ADAPTY_SECRET_KEY`) và gửi dưới dạng `Authorization: Api-Key {key}`.
- **Base URL**: Tất cả các request đều gửi đến `https://api.adapty.io`.
- **Cách xác định người dùng**: Gửi `adapty-customer-user-id` (ID người dùng của bạn — chỉ hoạt động nếu bạn xác định người dùng trong app) hoặc `adapty-profile-id` (ID hồ sơ người dùng của Adapty). Hai cái này có thể dùng thay thế nhau; chọn một trong hai.
## Kiểm tra gói đăng ký \{#check-a-subscription\}
Để kiểm tra trạng thái, gọi `getProfile` với phương thức `GET` và truyền định danh người dùng qua header — không có request body.
```javascript title="check-access.js"
const res = await fetch("https://api.adapty.io/api/v2/server-side-api/profile/", {
headers: {
"Authorization": `Api-Key ${process.env.ADAPTY_SECRET_KEY}`,
"adapty-customer-user-id": userId,
},
});
const { data } = await res.json();
function hasActiveAccess(profile, accessLevelId = "premium") {
const level = profile.access_levels?.find(a => a.access_level_id === accessLevelId);
if (!level) return false;
if (level.is_in_grace_period) return true;
if (!level.expires_at) return true; // lifetime / non-expiring
return new Date(level.expires_at) > new Date(); // not expired yet
}
if (hasActiveAccess(data)) {
// unlock premium features
}
```
Khác với hồ sơ người dùng từ SDK, response phía máy chủ **không có trường `is_active`**. Bạn cần tự suy ra trạng thái từ `access_levels[].expires_at`: `null` nghĩa là quyền truy cập trọn đời, ngày trong tương lai nghĩa là đang hoạt động, và ngày trong quá khứ nghĩa là đã hết hạn. Hãy coi `is_in_grace_period` là vẫn đang hoạt động. Để xem đầy đủ các trường của Profile và access level, xem [getProfile](https://adapty.io/docs/vi/api-adapty/operations/getProfile.md).
## Cấp quyền truy cập thủ công \{#grant-access-manually\}
Để mở khóa tính năng trả phí mà không cần mua hàng — mã promo, quyền truy cập cho nhà đầu tư hoặc beta tester, các trường hợp hỗ trợ — gọi `grantAccessLevel` với phương thức `POST`.
```javascript title="grant-access.js"
await fetch("https://api.adapty.io/api/v2/server-side-api/purchase/profile/grant/access-level/", {
method: "POST",
headers: {
"Authorization": `Api-Key ${process.env.ADAPTY_SECRET_KEY}`,
"adapty-customer-user-id": userId,
"Content-Type": "application/json",
},
body: JSON.stringify({ access_level_id: "premium" }), // add "expires_at" for temporary access
});
```
Có hai điều cần lưu ý:
- **Mức độ truy cập phải tồn tại sẵn** trong dashboard của bạn (**Access levels**) — `access_level_id` là định danh của nó, không phải tên mới.
- **Các cấp quyền thủ công không xuất hiện trong analytics**. Chúng chỉ được gửi đến tích hợp webhook và Event Feed, vì vậy các biểu đồ doanh thu và chuyển đổi sẽ không phản ánh chúng.
Để xem chi tiết về request và response, xem [grantAccessLevel](https://adapty.io/docs/vi/api-adapty/operations/grantAccessLevel.md).
## Xây dựng với AI coding agent của bạn \{#build-it-with-your-ai-coding-agent\}
Cung cấp cho AI coding agent hướng dẫn này và API spec dưới dạng Markdown (thêm `.md` vào bất kỳ URL trang nào), cho nó biết stack của bạn, và để nó viết các lời gọi:
- [OpenAPI spec](https://adapty.io/docs/vi/api-specs/adapty-api.yaml)
- [getProfile](https://adapty.io/docs/vi/api-adapty/operations/getProfile.md)
- [grantAccessLevel](https://adapty.io/docs/vi/api-adapty/operations/grantAccessLevel.md)
Ví dụ prompt:
```
Using the Adapty server-side API spec, write backend functions to check whether a
user has an active "premium" access level (GET /profile/, derive status from
expires_at — there's no is_active field) and to grant it (grantAccessLevel).
Authenticate with ADAPTY_SECRET_KEY and identify users by adapty-customer-user-id.
```
Agent sẽ viết code, nhưng nó không thể chạy backend của bạn hay thiết lập key — bạn cần tự cung cấp secret key và định danh người dùng.
## Giới hạn \{#limits\}
- **Giới hạn tốc độ**: Tối đa 40.000 request mỗi phút cho mỗi app.
- **Key theo từng app**: Mỗi key chỉ dùng được cho một app; hãy dùng đúng key cho đúng app.
- **Bắt buộc phải có một định danh**: Mỗi request cần có `adapty-customer-user-id` hoặc `adapty-profile-id`.
---
# File: test-purchases-in-sandbox
---
---
title: "Kiểm thử Sandbox"
description: "Kiểm thử in-app purchase trong môi trường sandbox để đảm bảo giao dịch diễn ra suôn sẻ."
---
Sau khi đã cấu hình xong mọi thứ trong Adapty Dashboard và ứng dụng mobile, đã đến lúc thực hiện kiểm thử in-app purchase.
**Lưu ý:** Không có công cụ kiểm thử nào tính phí người dùng khi họ thử mua sản phẩm. App Store không gửi email cho các giao dịch mua hoặc hoàn tiền được thực hiện trong môi trường kiểm thử.
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
2. Nhập thông tin người dùng kiểm thử. Hãy chắc chắn xác định **Country or Region** mà bạn muốn kiểm thử vì điều này ảnh hưởng đến tình trạng sẵn có của sản phẩm theo khu vực và đơn vị tiền tệ giao dịch.
:::tip
- Nếu bạn dùng Gmail hoặc iCloud, bạn có thể tái sử dụng địa chỉ email hiện có với [plus sign subaddressing](https://www.wikihow.com/Use-Plus-Addressing-in-Gmail).
- Bạn có thể dùng một địa chỉ email ngẫu nhiên thậm chí không tồn tại, nhưng hãy nhớ từ chối xác thực hai yếu tố (2FA) khi đăng nhập trên thiết bị kiểm thử sau này.
:::
3. Nhấn **Create**.
### Bước 2. Bật chế độ Developer \{#step-2-enable-the-developer-mode\}
:::note
Bỏ qua bước này nếu chế độ Developer **đã được bật** trên thiết bị kiểm thử của bạn hoặc nếu bạn **không có thiết bị Mac**.
:::
Bạn sẽ cần một máy Mac đã cài Xcode và cáp kết nối thiết bị kiểm thử:
1. Mở Xcode trên máy Mac. Nếu bạn định kiểm thử in-app purchase với TestFlight, bạn chỉ cần có XCode được cài đặt; bạn không cần phải có ứng dụng ở đó.
2. Kết nối thiết bị kiểm thử với máy Mac bằng cáp.
3. Vào **Settings > Privacy & Security > Developer Mode** trên thiết bị kiểm thử và bật **Developer Mode**.
### Bước 3. Tải ứng dụng từ TestFlight \{#step-3-download-the-app-from-testflight\}
:::info
Bước này chỉ áp dụng nếu bạn đang kiểm thử với TestFlight. Nếu bạn đang build ứng dụng trong Xcode, hãy bỏ qua bước này.
:::
Để biết chi tiết về cách gửi ứng dụng lên TestFlight, hãy xem [tài liệu của Apple](https://developer.apple.com/documentation/StoreKit/testing-in-app-purchases-with-sandbox#Prepare-for-sandbox-testing).
Trước khi tải ứng dụng TestFlight, trên thiết bị kiểm thử của bạn, hãy đảm bảo bạn đã đăng nhập bằng Apple Account thật (production) của mình. Sau đó tải ứng dụng cần kiểm thử từ TestFlight.
:::danger
Đừng mở ứng dụng ngay sau khi tải về. Hãy tiếp tục với các bước tiếp theo.
Nếu vô tình mở, hãy xóa ứng dụng khỏi thiết bị kiểm thử và tải lại. Nếu không, lịch sử mua hàng của bạn có thể không sạch và việc kiểm thử in-app purchase sẽ dẫn đến lỗi.
:::
### Bước 4. Chuyển sang tài khoản kiểm thử Sandbox \{#step-4-switch-to-sandbox-test-account\}
4. Cuộn xuống phần **Sandbox Apple Account** và nhấn **Sign In**.
5. Đăng nhập bằng thông tin đăng nhập của tài khoản Apple Sandbox.
### Bước 5. Xóa lịch sử mua hàng \{#step-5-clear-purchase-history\}
Nếu bạn vừa tạo tài khoản kiểm thử Sandbox mới và đã chuyển sang nó, bạn có thể bỏ qua bước này vì nó chỉ áp dụng cho việc kiểm thử lặp lại bằng cùng một tài khoản kiểm thử Sandbox.
1. Vào **Settings > Developer > Sandbox Apple Account** trên thiết bị kiểm thử.
2. Chọn **Manage** từ menu pop-up.
3. Vào **Account Settings** và nhấn **Clear Purchase History**.
:::danger
Bước này là bắt buộc mỗi khi bạn lặp lại việc kiểm thử bằng cùng một tài khoản kiểm thử Sandbox. Trong trường hợp này, bạn cũng sẽ cần [đăng xuất khỏi tài khoản kiểm thử Sandbox](#step-4-switch-to-sandbox-test-account), sau đó đăng nhập lại để xóa bộ nhớ cache lịch sử mua hàng trên thiết bị kiểm thử.
:::
### Bước 6. Build trong Xcode và chạy \{#step-6-build-in-xcode-and-run\}
:::info
Bước này chỉ áp dụng nếu bạn đang kiểm thử với bản build từ Xcode. Nếu bạn đang dùng TestFlight, hãy bỏ qua bước này.
:::
1. Kết nối thiết bị kiểm thử với máy Mac.
2. Mở Xcode.
3. Nhấn **Run** trong thanh công cụ hoặc chọn **Product > Run** để build và chạy ứng dụng trên thiết bị đã kết nối.
Nếu build thành công, Xcode sẽ khởi chạy ứng dụng trên thiết bị của bạn và mở phiên gỡ lỗi trong khu vực debug.
Ứng dụng của bạn giờ đã sẵn sàng để kiểm thử trên thiết bị.
### Bước 7. Thực hiện giao dịch mua kiểm thử \{#step-7-make-test-purchase\}
Mở ứng dụng và thực hiện giao dịch mua kiểm thử thông qua một paywall.
Sau khi hoàn tất, hãy xem bài viết về [xác nhận giao dịch mua kiểm thử](validate-test-purchases) để kiểm tra kết quả.
### Bước 8. Tiếp tục kiểm thử \{#step-8-keep-testing\}
Môi trường kiểm thử của bạn đã được thiết lập xong. Nếu muốn kiểm thử lại, hãy [xóa lịch sử mua hàng của tài khoản sandbox](https://developer.apple.com/help/app-store-connect/test-in-app-purchases/manage-sandbox-apple-account-settings/).
## Các vấn đề khi kiểm thử \{#testing-issues\}
Dưới đây là các vấn đề phổ biến bạn có thể gặp khi kiểm thử ứng dụng.
### Vấn đề với TestFlight \{#testflight-issues\}
Bạn không thể xóa lịch sử mua hàng **nếu dùng TestFlight mà không có tài khoản kiểm thử Sandbox**, dẫn đến các vấn đề khác nhau và kết quả kiểm thử không chính xác.
Nếu bạn vô tình quên [chuyển sang tài khoản kiểm thử Sandbox](#step-4-switch-to-sandbox-test-account) và đã mở ứng dụng dù chỉ một lần, TestFlight sẽ gán lịch sử mua hàng của bạn vào tài khoản Apple production, gây ra các vấn đề không mong muốn.
Để khắc phục, hãy làm theo các bước sau:
1. Xóa ứng dụng khỏi thiết bị kiểm thử.
2. Làm theo các bước [Kiểm thử Sandbox](#sandbox-testing).
:::note
Quan trọng là không chỉ cài lại ứng dụng, mà còn phải chuyển sang tài khoản kiểm thử Sandbox, xóa lịch sử mua hàng và khởi chạy ứng dụng bằng tài khoản kiểm thử Sandbox.
:::
### Vấn đề với mức độ truy cập được chia sẻ \{#shared-access-levels-issues\}
Nếu bạn lặp lại kiểm thử bằng cùng một tài khoản kiểm thử Sandbox, bạn có thể gặp hành vi bất ngờ với [mức độ truy cập được chia sẻ](sharing-paid-access-between-user-accounts) cho người dùng kiểm thử.
Để kiểm tra xem người dùng có mức độ truy cập kế thừa hay không, hãy vào [Profiles & Segments](https://app.adapty.io/profiles/users) từ Adapty Dashboard và mở hồ sơ người dùng.
Nếu người dùng có mức độ truy cập kế thừa, hãy làm theo các bước sau để có kết quả kiểm thử chính xác:
1. Xóa hồ sơ cha.
2. Xóa ứng dụng khỏi thiết bị kiểm thử.
3. [Tải ứng dụng từ TestFlight](#step-3-download-the-app-from-testflight).
4. [Chuyển sang tài khoản kiểm thử Sandbox](#step-4-switch-to-sandbox-test-account).
5. [Xóa lịch sử mua hàng](#step-5-clear-purchase-history).
6. [Mở ứng dụng và thực hiện giao dịch mua kiểm thử](#step-6-make-test-purchase).
:::note
Xóa lịch sử mua hàng là thao tác đặt lại giao dịch mua ở phía cửa hàng. Xóa hồ sơ cha chỉ xóa bản ghi ở phía Adapty. Để hiểu tại sao tài khoản được dùng lại vẫn giữ quyền truy cập và những thao tác đặt lại nào thực sự có hiệu quả, hãy xem [Đặt lại gói đăng ký của người kiểm thử](#resetting-a-testers-subscription).
:::
### Cập nhật ứng dụng trong TestFlight \{#updating-app-in-testflight\}
Nếu ứng dụng TestFlight đã được cập nhật:
1. Xóa ứng dụng khỏi thiết bị kiểm thử.
2. [Tải ứng dụng từ TestFlight](#step-3-download-the-app-from-testflight).
3. [Chuyển sang tài khoản kiểm thử Sandbox](#step-4-switch-to-sandbox-test-account).
4. [Xóa lịch sử mua hàng](#step-5-clear-purchase-history).
5. [Mở ứng dụng và thực hiện giao dịch mua kiểm thử](#step-6-make-test-purchase).
## Đặt lại gói đăng ký của người kiểm thử \{#resetting-a-testers-subscription\}
Trong môi trường sandbox, một giao dịch mua thuộc về **tài khoản Apple sandbox**, không phải hồ sơ người dùng Adapty. Các thao tác bạn thực hiện trên hồ sơ — xóa nó hoặc chỉnh sửa mức độ truy cập — không xóa giao dịch mua khỏi tài khoản cửa hàng. Lần cài đặt lại hoặc đồng bộ tiếp theo, SDK sẽ gán lại cùng giao dịch đó và người kiểm thử lại có quyền truy cập.
Bảng dưới đây cho thấy mỗi thao tác đặt lại thay đổi điều gì và người kiểm thử thấy gì sau đó.
| Thao tác | Hồ sơ người dùng Adapty | Tài khoản Apple sandbox | Quyền truy cập của người kiểm thử sau đó |
| :-------------------------------------------------------------------------------- | :------------------------------------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------- |
| Xóa hồ sơ trong Adapty Dashboard | Đã xóa | Không thay đổi | **Trở lại** — khi cài lại, một hồ sơ mới sẽ gán lại cùng chuỗi giao dịch |
| Xóa hồ sơ qua [Delete profile API](api-adapty/operations/deleteProfile) | Đã xóa | Không thay đổi | **Trở lại** — tương tự như xóa trong Dashboard |
| Thêm ngày hết hạn trong quá khứ qua **Add access level** | Bị ghi đè ở lần đồng bộ tiếp theo | Không thay đổi | **Trở lại** ở lần gia hạn tiếp theo — gói đăng ký đang hoạt động sẽ áp dụng lại ngày hết hạn trong tương lai |
| Gọi [Revoke access level API](api-adapty/operations/revokeAccessLevel) | Hết hạn ngay, kích hoạt `access_level_updated` (`is_active=false`) | Không thay đổi | **Trở lại** ở lần gia hạn hoặc cài lại tiếp theo — không phải cách đặt lại sandbox đáng tin cậy |
| Hủy gói đăng ký trong tài khoản sandbox | Không thay đổi trực tiếp | Gói đăng ký đã bị hủy | Việc gia hạn dừng lại, quyền truy cập kết thúc khi chu kỳ hiện tại hết hạn, và người kiểm thử có thể mua lại sản phẩm |
| Đăng nhập bằng tài khoản Apple sandbox mới | Hồ sơ mới | Tài khoản mới, trống | **Sạch** — được khuyến nghị cho kiểm thử lặp lại |
### Đặt lại người kiểm thử về trạng thái sạch \{#reset-a-tester-to-a-clean-state\}
Để kiểm thử flow mua hàng lặp lại, hãy dùng một tài khoản Apple sandbox mới cho mỗi lần kiểm thử thay vì đặt lại hồ sơ. Làm theo [Bước 1](#step-1-create-sandbox-test-account-in-app-store-connect) để tạo tài khoản và [Bước 4](#step-4-switch-to-sandbox-test-account) để chuyển sang nó trên thiết bị. Nếu bạn tái sử dụng tài khoản sandbox hiện có, hãy [xóa lịch sử mua hàng của nó](#step-5-clear-purchase-history) trước — xóa hồ sơ Adapty không xóa được lịch sử đó.
### Xóa quyền truy cập của người kiểm thử hiện tại \{#remove-access-from-an-existing-tester\}
Để xóa quyền truy cập của người kiểm thử, đừng đặt ngày hết hạn về quá khứ hoặc gọi Revoke access level API. Trong sandbox, gói đăng ký tự gia hạn sau vài phút. Mỗi lần gia hạn sẽ khôi phục lại ngày hết hạn trong tương lai trên cùng chuỗi giao dịch, vì vậy quyền truy cập sẽ tự động trở lại. Revoke access level API có kích hoạt sự kiện `access_level_updated` (`is_active=false`), nhưng lần gia hạn tiếp theo sẽ ghi đè nó.
Để thực sự dừng quyền truy cập, hãy hủy gói đăng ký ở phía cửa hàng. Trên thiết bị kiểm thử, vào **Settings > Developer > Sandbox Apple Account**, chọn **Manage** và hủy gói đăng ký. Việc gia hạn sẽ dừng và quyền truy cập kết thúc khi chu kỳ hiện tại hết hạn.
### Tại sao xóa hồ sơ lại khiến quyền truy cập trở lại \{#why-deleting-the-profile-brings-access-back\}
Khi người kiểm thử cài lại ứng dụng, Adapty nhận lịch sử mua hàng của tài khoản sandbox và liên kết lần cài đặt mới với giao dịch hiện có. Giao dịch mua được gắn với tài khoản cửa hàng, không phải với hồ sơ bạn đã xóa.
- **Hồ sơ ẩn danh**: Việc cài lại mà không có `customer_user_id` luôn kế thừa mức độ truy cập của tài khoản cửa hàng, bất kể cài đặt [chia sẻ quyền truy cập có phí](sharing-paid-access-between-user-accounts) của bạn.
- **Hồ sơ đã xác định**: Quyền truy cập có được chuyển sang `customer_user_id` mới hay không phụ thuộc vào cài đặt chia sẻ quyền truy cập có phí của bạn.
Để biết cách Adapty liên kết các hồ sơ này thành một chuỗi, hãy xem [Cách hồ sơ hoạt động](how-profiles-work#parent-and-inheritor-profiles).
## Gói đăng ký kiểm thử \{#test-subscriptions\}
Khi kiểm thử ứng dụng bằng tài khoản kiểm thử Sandbox, bạn có thể thiết lập tốc độ gia hạn gói đăng ký cho mỗi người kiểm thử trong sandbox. Tìm hiểu thêm về cách chỉnh sửa tốc độ gia hạn gói đăng ký trong [tài liệu chính thức của Apple](https://developer.apple.com/help/app-store-connect/test-in-app-purchases/manage-sandbox-apple-account-settings).
Mặc định, gói đăng ký gia hạn tối đa 12 lần trước khi dừng, theo lịch sau:
| Thời hạn gói đăng ký | 1 tuần | 1 tháng | 2 tháng | 3 tháng | 6 tháng | 1 năm |
| :----------------------------- | :--------- | :--------- | :--------- | :--------- | :--------- | :--------- |
| Tốc độ gia hạn gói đăng ký | 3 phút | 5 phút | 10 phút | 15 phút | 30 phút | 1 giờ |
| Thời gian thử lại thanh toán | 10 phút | 10 phút | 10 phút | 10 phút | 10 phút | 10 phút |
| Thời gian ân hạn thanh toán | 3 phút | 5 phút | 5 phút | 5 phút | 5 phút | 5 phút |
:::note
Lưu ý rằng các giao dịch kiểm thử có thể mất đến 10 phút để xuất hiện trong [Event feed](validate-test-purchases).
:::
Hãy dùng sandbox để xác nhận rằng ứng dụng và backend của bạn xử lý đúng các lần gia hạn, thử lại thanh toán và thời gian ân hạn — không phải để dự đoán thời gian gia hạn trong môi trường production. Lịch gia hạn được tăng tốc và giới hạn ở trên không phản ánh thực tế production. Để phát lại giao dịch trên server phục vụ kiểm thử backend, hãy dùng [Set transaction API](api-adapty/operations/setTransaction).
## Kiểm thử ưu đãi \{#test-offers\}
Việc kiểm thử ưu đãi yêu cầu tất cả biên lai của người dùng phải được xóa để tính đủ điều kiện hoạt động chính xác.
Cách đáng tin cậy nhất để kiểm thử ưu đãi là dùng một [tài khoản kiểm thử Sandbox](#step-1-create-sandbox-test-account-in-app-store-connect) hoàn toàn mới. Việc kiểm thử lặp lại bằng cùng một tài khoản kiểm thử Sandbox có thể gây ra hành vi bất ngờ.
:::danger
Nếu bạn lặp lại kiểm thử bằng cùng một tài khoản kiểm thử Sandbox, hãy nhớ [xóa lịch sử mua hàng](#step-5-clear-purchase-history) để tránh các vấn đề liên quan đến điều kiện đủ điều kiện.
:::
---
# File: local-sk-files
---
---
title: "Kiểm thử StoreKit trong Xcode"
description: "Kiểm thử in-app purchase trong môi trường sandbox để đảm bảo giao dịch diễn ra suôn sẻ."
---
Kiểm thử StoreKit trong Xcode cho phép bạn kiểm thử in-app purchase cục bộ mà không cần thiết lập tài khoản sandbox.
Để thực hiện kiểu kiểm thử này, bạn cần:
1. [Tạo sản phẩm trong Adapty](quickstart-products) và gán cho nó một **App Store product ID**.
2. Trong Xcode, tạo một [tệp cấu hình StoreKit](https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode) cục bộ và thêm sản phẩm vào đó. Product ID phải trùng với **App Store product ID** trong Adapty.
3. Thêm tệp cấu hình StoreKit vào build scheme và build ứng dụng. Khởi chạy trên emulator hoặc trên thiết bị thực.
## Có nên dùng kiểm thử StoreKit trong Xcode không? \{#should-i-use-storekit-testing-in-xcode\}
Cách kiểm thử này tiện nhất nếu bạn là nhà phát triển ứng dụng muốn kiểm thử build nhanh hoặc muốn thử các tình huống mua hàng khác nhau bằng các tính năng của Xcode.
Tuy nhiên, cần lưu ý rằng kiểu kiểm thử này là cục bộ, nên sẽ không có thay đổi nào hiển thị trên Adapty dashboard. Trước khi phát hành ứng dụng trong môi trường production, chúng tôi khuyến nghị bạn kiểm thử [làm việc với hồ sơ người dùng](ios-quickstart-identify) bằng [môi trường sandbox](test-purchases-in-sandbox).
Bạn **nên** dùng kiểm thử StoreKit nếu muốn:
- Kiểm thử logic mua hàng
- Tái hiện các tình huống mua hàng khác nhau bằng công cụ Xcode (ví dụ: thanh toán bị hủy hoặc hoàn tiền)
- Kiểm thử trên emulator
Bạn **không nên** dùng kiểm thử StoreKit nếu muốn:
- Kiểm thử logic liên quan đến hồ sơ người dùng
- Xem các thao tác trong ứng dụng có hiển thị trên Adapty dashboard không
- Chia sẻ ứng dụng với các nhóm không phải nhà phát triển để kiểm thử
## Bước 1. Tạo tệp cấu hình StoreKit \{#step-1-create-a-storekit-configuration-file\}
Để tạo tệp cấu hình StoreKit trong Xcode:
1. Nhấp vào **File > New > File from template**. Sau đó, chọn **StoreKit Configuration File** và nhấp **Next**.
2. Đặt tên cho tệp. Sau đó, tùy thuộc vào việc bạn đã có sản phẩm trong App Store Connect chưa:
- Chọn **Sync this file with an app in App Store Connect**: Để tạo tệp cấu hình chứa tất cả sản phẩm App Store Connect của bạn, giúp kiểm thử cục bộ.
- Không chọn **Sync this file with an app in App Store Connect**: Để tạo tệp cấu hình trống, nơi bạn sẽ cần thêm sản phẩm thủ công.
Nhấp **Next**.
3. Không thêm ứng dụng làm target. Chỉ cần tiếp tục. Nếu bạn đang làm việc với sản phẩm đã đồng bộ từ App Store Connect, chuyển đến [Bước 2](#step-2-add-the-configuration-file-to-the-build-scheme).
4. Nếu sản phẩm của bạn chưa được đồng bộ từ App Store Connect, nhấp **+** ở góc dưới bên trái và chọn loại sản phẩm.
5. Nhập tên nhóm gói đăng ký và nhấp **Next**.
6. Nhập tên tham chiếu. Trong trường **Product ID**, nhập **App Store product ID** của sản phẩm trong Adapty.
7. Cấu hình giá, ưu đãi và các thiết lập sản phẩm khác trong tệp cấu hình. Hoặc thêm nhiều sản phẩm hơn vào đó.
## Bước 2. Thêm tệp cấu hình vào build scheme \{#step-2-add-the-configuration-file-to-the-build-scheme\}
Để build ứng dụng với tệp cấu hình này, bạn cần thêm nó vào build scheme. Thực hành tốt nhất là tách riêng scheme kiểm thử và scheme production, vì vậy chúng tôi đề xuất bạn tạo một scheme mới cho việc kiểm thử:
1. Ở trên cùng, nhấp vào tên ứng dụng và chọn **New scheme**.
2. Nhập tên cho scheme và nhấp **OK**.
3. Nhấp vào tên ứng dụng một lần nữa và chọn **Edit scheme**. Trong phần **StoreKit configuration**, chọn tệp cấu hình cục bộ của bạn để nó được sử dụng khi build.
## Bước 3. Build & kiểm thử \{#step-3-build--test\}
Bây giờ, bạn có thể build ứng dụng và kiểm thử in-app purchase mà không cần kết nối đến backend của App Store. Bạn có thể mua sản phẩm và nhận mức độ truy cập cục bộ. Những thay đổi này sẽ không được phản ánh trên Adapty dashboard, nhưng bạn vẫn có thể kiểm thử việc mở khóa các tính năng trả phí cục bộ.
[Đọc thêm](https://developer.apple.com/documentation/xcode/testing-in-app-purchases-with-storekit-transaction-manager-in-code) về các tính năng khác có sẵn khi kiểm thử StoreKit trong Xcode.
---
# File: testing-on-android
---
---
title: "Kiểm thử in-app purchase trong Google Play Store"
description: "Kiểm thử mua gói đăng ký trên Android bằng Adapty."
---
Kiểm thử in-app purchase (IAP) trong ứng dụng Android là bước quan trọng trước khi phát hành app ra công chúng. Kiểm thử sandbox là cách an toàn và hiệu quả để kiểm tra IAP mà không tốn tiền thật của người dùng. Trong hướng dẫn này, chúng ta sẽ cùng tìm hiểu quy trình kiểm thử sandbox IAP trên Google Play Store cho Android.
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
2. Chọn danh sách license testers hiện có hoặc tạo danh sách mới.
3. Thêm tài khoản bạn sẽ dùng để test vào danh sách và lưu thay đổi. Nếu các thành viên trong nhóm cũng cần test ứng dụng, bạn có thể thêm email của họ vào danh sách để cả nhóm đều được cấp quyền truy cập.
## 3. Tạo closed track và thêm tài khoản test vào đó \{#3-create-closed-track-and-add-test-account-to-it\}
Để bắt đầu kiểm thử, bạn cần publish một phiên bản đã ký của ứng dụng lên closed track:
1. Mở ứng dụng của bạn và chọn **Test and release > Testing > Closed testing** trong menu. Tại đó, nhấn **Create track**.
2. Nhập tên cho closed testing track và nhấn **Create track**.
3. Thêm danh sách testers vào track.
4. Trong phần **How testers join your test**, sao chép đường link và gửi đến thiết bị đã đăng nhập tài khoản test. Mở link trên thiết bị test để đăng ký người dùng đó làm tester.
:::warning
Lưu ý những điều sau để đảm bảo kiểm thử thành công:
- Mở URL opt-in sẽ đánh dấu tài khoản Play của bạn cho việc kiểm thử. Nếu bạn bỏ qua bước này, các sản phẩm sẽ không tải được.
- Thông thường, các nhà phát triển sẽ dùng application ID khác cho bản build test. Điều này sẽ gây ra vấn đề vì Google Play Services dùng application ID để tìm in-app purchase của bạn.
- Trong một số trường hợp, tài khoản test có thể mua được consumable nhưng không mua được gói đăng ký, nếu thiết bị test chưa có mã PIN. Lỗi này có thể hiển thị thông báo mơ hồ "Something went wrong". Hãy đảm bảo thiết bị test đã có mã PIN và đã đăng nhập vào Google Play Store.
:::
## 4. Tải APK đã ký lên closed track \{#4-upload-a-signed-apk-to-the-closed-track\}
Tạo APK đã ký hoặc dùng Android App Bundle để tải APK đã ký lên closed track vừa tạo. Bạn không cần phải triển khai bản phát hành, chỉ cần tải APK lên là đủ. Bạn có thể tìm hiểu thêm về vấn đề này trong [bài viết hỗ trợ này](https://support.google.com/googleplay/android-developer/answer/9859348?visit_id=638929100639477968-3849460621&rd=1).
:::important
Nếu ứng dụng của bạn còn mới, bạn có thể cần phải mở khả dụng ứng dụng ở quốc gia hoặc khu vực của mình. Để thực hiện, vào **Testing > Closed testing**, nhấn vào test track của bạn, rồi đến **Countries/regions** để thêm các quốc gia và khu vực mong muốn.
:::
## 5. Kiểm thử in-app purchase \{#5-test-in-app-purchases\}
Sau khi tải APK lên, hãy chờ vài phút để bản phát hành được xử lý. Sau đó, mở thiết bị test và đăng nhập bằng tài khoản email đã thêm vào danh sách Testers. Lúc này bạn có thể kiểm thử in-app purchase như trên ứng dụng thực tế.
## Đọc thêm \{#read-more\}
Tham khảo các tài nguyên sau để tìm hiểu thêm về kiểm thử in-app purchase trong ứng dụng Android:
- [Chu kỳ gia hạn trong sandbox](https://developer.android.com/google/play/billing/test#subs)
- [Kiểm thử sản phẩm mua một lần](https://developer.android.com/google/play/billing/test#one-time)
---
# File: validate-test-purchases
---
---
title: "Xác thực giao dịch mua thử nghiệm"
description: "Xác thực giao dịch mua thử nghiệm trong Adapty để đảm bảo giao dịch diễn ra suôn sẻ."
---
Trước khi phát hành ứng dụng di động lên môi trường production, việc kiểm thử in-app purchase một cách kỹ lưỡng là rất quan trọng. Hãy tham khảo các bài viết [Kiểm thử in-app purchase trên Apple App Store](test-purchases-in-sandbox) và [Kiểm thử in-app purchase trên Google Play Store](testing-on-android) để được hướng dẫn chi tiết. Sau khi bắt đầu kiểm thử, bạn cần xác nhận rằng các giao dịch mua thử nghiệm đã thành công.
Mỗi khi thực hiện một giao dịch mua thử nghiệm trên thiết bị di động, hãy xem giao dịch tương ứng trong [**Event Feed**](https://app.adapty.io/event-feed) trên Adapty Dashboard. Nếu giao dịch không xuất hiện trong **Event Feed**, nghĩa là Adapty chưa ghi nhận được giao dịch đó.
## Giao dịch mua thử nghiệm thành công \{#test-purchase-is-successful\}
Nếu giao dịch mua thử nghiệm thành công, sự kiện giao dịch tương ứng sẽ hiển thị trong **Event Feed**:
Nếu các giao dịch hoạt động đúng như mong đợi, hãy chuyển sang [Danh sách kiểm tra trước khi phát hành](release-checklist) và tiến hành phát hành ứng dụng.
## Giao dịch mua thử nghiệm không thành công \{#test-purchase-is-not-successful\}
Nếu không thấy sự kiện giao dịch nào trong vòng 10 phút hoặc gặp lỗi trong ứng dụng di động, hãy tham khảo bài viết [Xử lý sự cố](troubleshooting-test-purchases) và các bài viết về xử lý lỗi [cho iOS](ios-sdk-error-handling), [cho Android](android-sdk-error-handling), [cho React Native](react-native-handle-errors), [cho Flutter](error-handling-on-flutter-react-native-unity), [cho Unity](unity-handle-errors), và [Kotlin Multiplatform](kmp-handle-errors) để tìm giải pháp phù hợp.
---
# File: troubleshooting-test-purchases
---
---
title: "Khắc phục sự cố mua hàng thử nghiệm"
description: "Khắc phục sự cố mua hàng thử nghiệm trong Adapty và giải quyết các vấn đề giao dịch in-app phổ biến."
---
Nếu bạn gặp sự cố giao dịch, trước tiên hãy đảm bảo bạn đã hoàn thành tất cả các bước trong [danh sách kiểm tra phát hành](release-checklist). Nếu đã hoàn thành tất cả các bước mà vẫn gặp sự cố, hãy làm theo hướng dẫn dưới đây để giải quyết:
## Ứng dụng di động trả về lỗi \{#an-error-is-returned-in-the-mobile-app\}
Tham khảo danh sách lỗi theo nền tảng của bạn: [dành cho iOS](ios-sdk-error-handling), [dành cho Android](android-sdk-error-handling), [dành cho React Native](react-native-troubleshoot-purchases), [Flutter](error-handling-on-flutter-react-native-unity), và [Unity](unity-troubleshoot-purchases), rồi làm theo khuyến nghị của chúng tôi để khắc phục sự cố.
## Giao dịch không xuất hiện trong Event Feed dù ứng dụng không trả về lỗi \{#transaction-is-absent-from-the-event-feed-although-no-error-is-returned-in-the-mobile-app\}
Để khắc phục sự cố này, hãy kiểm tra các điểm sau:
1. **Dành cho iOS**: Đảm bảo bạn dùng thiết bị thật thay vì máy ảo simulator.
2. Đảm bảo `Bundle ID`/`Package name` của ứng dụng khớp với giá trị trong [**App settings**](https://app.adapty.io/settings/general).
3. Đảm bảo `PUBLIC_SDK_KEY` trong ứng dụng khớp với **Public SDK key** trên Adapty Dashboard: [**App settings** -> tab **General** -> mục **API keys**](https://app.adapty.io/settings/general).
4. Đảm bảo bạn đang dùng tài khoản sandbox — không phải [file cấu hình StoreKit cục bộ](local-sk-files). Nếu trước đây bạn đã dùng file cấu hình StoreKit cục bộ để kiểm thử, hãy đảm bảo bạn không dùng nó trong bản build hiện tại.
## Hồ sơ thử nghiệm không có sự kiện nào \{#no-event-is-present-in-my-testing-profile\}
Đây là hành vi bình thường. Một bản ghi hồ sơ người dùng mới sẽ được tự động tạo trong Adapty khi:
- Người dùng chạy ứng dụng lần đầu tiên
- Người dùng đăng xuất khỏi ứng dụng
**Lý do xảy ra:** Tất cả giao dịch và sự kiện đều gắn với hồ sơ người dùng đã tạo ra giao dịch đầu tiên. Điều này giúp toàn bộ lịch sử giao dịch (dùng thử, mua hàng, gia hạn) được liên kết với cùng một hồ sơ.
**Những gì bạn sẽ thấy:** Các bản ghi hồ sơ người dùng mới (gọi là "hồ sơ không gốc") có thể xuất hiện mà không có sự kiện nào nhưng vẫn giữ nguyên mức độ truy cập. Bạn có thể thấy sự kiện `access_level_updated`. Đây là hành vi bình thường.
**Khi kiểm thử:** Để tránh tạo nhiều hồ sơ, hãy tạo tài khoản thử nghiệm mới (Sandbox Apple ID) mỗi lần bạn cài đặt lại ứng dụng.
Xem thêm chi tiết tại [Tạo hồ sơ](how-profiles-work#profile-creation).
Dưới đây là ví dụ về một hồ sơ không gốc. Lưu ý rằng không có sự kiện nào trong **User history** nhưng vẫn có mức độ truy cập.
## Giá không phản ánh đúng giá đã đặt trong App Store Connect \{#prices-do-not-reflect-the-actual-prices-set-in-app-store-connect\}
Trong cả Sandbox và TestFlight (vốn dùng môi trường sandbox cho in-app purchase), điều quan trọng là xác minh luồng mua hàng hoạt động đúng, thay vì tập trung vào độ chính xác của giá. Cần lưu ý rằng API của Apple đôi khi có thể cung cấp dữ liệu không chính xác, đặc biệt khi các thiết bị hoặc tài khoản được cấu hình với các khu vực khác nhau. Vì giá đến trực tiếp từ Store và backend Adapty không ảnh hưởng đến giá mua hàng theo bất kỳ cách nào, bạn có thể bỏ qua sự không chính xác về giá khi kiểm thử mua hàng qua Adapty.
Do đó, hãy ưu tiên kiểm thử luồng mua hàng thay vì độ chính xác của giá để đảm bảo nó hoạt động đúng như mong đợi.
## Thời gian giao dịch trong Event Feed không chính xác \{#the-transaction-time-in-the-event-feed-is-incorrect\}
**Event Feed** sử dụng múi giờ được đặt trong **App Settings**. Để đồng bộ múi giờ của sự kiện với giờ địa phương của bạn, hãy điều chỉnh **Reporting timezone** trong [**App settings** -> tab **General**](https://app.adapty.io/settings/general).
## Paywall và sản phẩm mất nhiều thời gian để tải \{#paywalls-and-products-take-a-long-time-to-load\}
Sự cố này có thể xảy ra nếu tài khoản thử nghiệm của bạn có lịch sử giao dịch dài. Chúng tôi khuyến nghị tạo tài khoản thử nghiệm mới mỗi lần, như đã nêu trong phần [Tạo Tài khoản Thử nghiệm Sandbox (Sandbox Apple ID) trong App Store Connect](test-purchases-in-sandbox#step-1-create-sandbox-test-account-in-app-store-connect).
Nếu bạn không thể tạo tài khoản mới, bạn có thể xóa lịch sử giao dịch trên tài khoản hiện tại bằng cách thực hiện các bước sau trên thiết bị iOS:
1. Mở **Settings** và nhấn **App Store**.
2. Nhấn **Sandbox Apple ID** của bạn.
3. Trong cửa sổ popup, chọn **Manage**.
4. Trên trang **Account Settings**, nhấn **Clear Purchase History**.
Để biết thêm chi tiết, hãy xem [tài liệu dành cho nhà phát triển Apple](https://developer.apple.com/documentation/storekit/testing-in-app-purchases-with-sandbox).
---
# File: test-devices
---
---
title: "Thiết bị kiểm thử"
description: "Tìm hiểu cách quản lý thiết bị kiểm thử trong Adapty để kiểm thử ứng dụng hiệu quả."
---
Để phục vụ kiểm thử, bạn có thể đánh dấu thiết bị của mình là thiết bị kiểm thử — điều này sẽ tắt bộ nhớ đệm và đảm bảo các thay đổi được phản ánh ngay lập tức.
:::note
Thiết bị kiểm thử được hỗ trợ từ các phiên bản SDK cụ thể:
- iOS: 2.11.1
- Android: 2.11.3
- React Native: 2.11.1
Hỗ trợ cho Flutter và Unity sẽ được bổ sung sau.
:::
## Đánh dấu thiết bị là thiết bị kiểm thử \{#mark-your-device-as-test\}
1. Mở [**App settings**](https://app.adapty.io/settings/general) trong Adapty Dashboard.
2. Kéo xuống phần **Test devices** trong tab **General**.
3. Nhấp vào nút **Add test device**.
4. Trong cửa sổ **Add test device**, nhập:
| Trường | Mô tả |
|:-----------------------------------------| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Test device name** | Tên của thiết bị kiểm thử để bạn dễ nhận biết. |
| **ID used to identify this test device** | Chọn loại định danh bạn muốn dùng để xác định thiết bị kiểm thử. Xem khuyến nghị của chúng tôi trong phần [Nên dùng định danh nào](test-devices#which-identifier-you-should-use) bên dưới để chọn tùy chọn phù hợp nhất. |
| **ID value** | Nhập giá trị của định danh. |
5. Nhớ nhấp vào nút **Add test device** để lưu thay đổi.
## Nên dùng định danh nào \{#which-identifier-you-should-use\}
Để xác định một thiết bị, bạn có thể dùng nhiều loại định danh khác nhau. Chúng tôi khuyến nghị:
- **Customer User ID** cho cả thiết bị iOS và Android nếu bạn Một định danh duy nhất do bạn thiết lập để xác định người dùng trong hệ thống của bạn. Có thể là email của người dùng, ID nội bộ của bạn, hoặc bất kỳ chuỗi nào khác. Để dùng tùy chọn này, bạn phải
Đây là lựa chọn tốt nhất để xác định thiết bị kiểm thử, đặc biệt nếu bạn dùng nhiều thiết bị cho cùng một tài khoản. Tất cả các thiết bị có tài khoản này đều sẽ được coi là thiết bị kiểm thử.
| | Adapty profile ID |Một định danh duy nhất cho [hồ sơ người dùng](profiles-crm) trong Adapty.
Dùng tùy chọn này nếu bạn không thể dùng Customer User ID, IDFA cho iOS hoặc Advertising ID cho Android. Lưu ý rằng Adapty Profile ID có thể thay đổi nếu bạn cài lại ứng dụng hoặc đăng nhập lại.
| #### Cách lấy Customer User ID và Adapty profile ID \{#how-to-obtain-customer-user-id-and-adapty-profile-id\} Cả hai định danh đều có thể lấy được trong phần chi tiết **Profile** trên Adapty Dashboard: 1. Tìm hồ sơ người dùng trong tab [**Adapty Profiles** -> **Event feed**](https://app.adapty.io/event-feed). :::note Để tìm đúng hồ sơ, hãy thực hiện một loại giao dịch hiếm. Khi giao dịch xuất hiện trong [**Event Feed**](https://app.adapty.io/event-feed), bạn sẽ dễ dàng nhận ra nó. ::: 2. Sao chép giá trị của trường **Customer user ID** và **Adapty ID** trong phần chi tiết hồ sơ:
### Định danh Apple \{#apple-identifiers\}
| Định danh | Cách dùng |
|----------|-----|
| IDFA | Identifier for Advertisers (IDFA) là định danh thiết bị duy nhất do Apple gán cho thiết bị của người dùng.
Đây là lựa chọn lý tưởng cho thiết bị iOS vì nó không tự thay đổi, mặc dù bạn có thể đặt lại thủ công.
**Lưu ý**: Từ khi iOS 14.5 ra mắt, nhà quảng cáo phải xin sự đồng ý của người dùng để truy cập IDFA. Hãy đảm bảo ứng dụng của bạn đang yêu cầu sự đồng ý và bạn đã cấp quyền trên thiết bị kiểm thử của mình.
| | IDFV | Identifier for Vendors (IDFV) là định danh chữ-số duy nhất do Apple gán cho tất cả các ứng dụng trên cùng một thiết bị từ cùng một nhà phát hành/vendor. Nó có thể thay đổi nếu bạn cài lại hoặc cập nhật ứng dụng. | #### Cách lấy IDFA \{#how-to-obtain-the-idfa\} Apple không cung cấp IDFA theo mặc định. Lấy nó từ phần attribution trong hồ sơ người dùng trên Adapty Dashboard: 1. Tìm hồ sơ người dùng trong tab [**Adapty Profiles** -> **Event feed**](https://app.adapty.io/event-feed). :::note Để tìm đúng hồ sơ, hãy thực hiện một loại giao dịch hiếm. Khi giao dịch xuất hiện trong [**Event Feed**](https://app.adapty.io/event-feed), bạn sẽ dễ dàng nhận ra nó. ::: 2. Mở chi tiết hồ sơ và sao chép giá trị trường **IDFA** trong phần **Attributes**:
Ngoài ra, bạn có thể [tìm ứng dụng trên App Store sẽ hiển thị IDFA của bạn](https://www.apple.com/us/search/idfa?src=globalnav).
#### Cách lấy Identifier for Vendors (IDFV) \{#how-to-obtain-the-identifier-for-vendors-idfv\}
Để lấy IDFV, hãy yêu cầu developer của bạn gọi phương thức sau trong ứng dụng và hiển thị định danh nhận được ra log hoặc debug panel.
```swift showLineNumbers title="Swift"
UIDevice.current.identifierForVendor
```
### Định danh Google \{#google-identifiers\}
| Định danh | Cách dùng |
|----------|-----|
| Advertising ID | Advertising ID là định danh thiết bị duy nhất do Google gán cho thiết bị của người dùng.
Đây là lựa chọn lý tưởng cho thiết bị Android vì nó không tự thay đổi, mặc dù bạn có thể đặt lại thủ công.
**Lưu ý**: Để dùng tùy chọn này, hãy tắt **Opt out of Ads Personalization** trong cài đặt **Ads** nếu bạn đang dùng Android 12 trở lên.
| | Android ID | Android ID là định danh duy nhất cho mỗi tổ hợp khóa ký ứng dụng, người dùng và thiết bị. Có sẵn trên Android 8.0 trở lên. | #### Cách lấy Advertising ID \{#how-to-obtain-advertising-id\} Để tìm Advertising ID của thiết bị: 1. Mở ứng dụng **Settings** trên thiết bị Android của bạn. 2. Nhấp vào **Google**. 3. Chọn **Ads** trong **Services**. Advertising ID của bạn sẽ hiển thị ở cuối màn hình. #### Cách lấy Android ID \{#how-to-obtain-android-id\} Để lấy Android ID, hãy yêu cầu developer của bạn truy vấn [ANDROID_ID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID) bằng phương thức sau trong ứng dụng và hiển thị định danh nhận được ra log hoặc debug panel. ```kotlin showLineNumbers title="Kotlin/Java" android.provider.Settings.Secure.getString(contentResolver, android.provider.Settings.Secure.ANDROID_ID); ``` --- # File: release-checklist --- --- title: "Danh sách kiểm tra trước khi phát hành" description: "Làm theo danh sách kiểm tra của Adapty để đảm bảo quá trình cập nhật ứng dụng diễn ra suôn sẻ." --- Chúng tôi rất vui khi bạn quyết định sử dụng Adapty! Hy vọng quá trình tích hợp diễn ra suôn sẻ. Hướng dẫn này sẽ đưa bạn qua các bước để đảm bảo ứng dụng sẵn sàng phát hành lên cửa hàng, và bạn có thể yên tâm rằng flow thanh toán hoạt động đúng. ## Các yêu cầu cần có trước \{#pre-flight-essentials\} Những gì bạn cần trước khi bắt đầu kiểm tra: - Thiết bị thật với tài khoản sandbox - Quyền truy cập vào Adapty Dashboard - Quyền truy cập vào App Store Connect / Google Play Console :::note Mặc dù có thể thực hiện mua hàng sandbox trên máy ảo, nhưng bạn cần thiết bị thật để kiểm tra đầy đủ tất cả các flow, bao gồm cả hộp thoại thanh toán và xác thực sinh trắc học. ::: ## Kiểm tra chung \{#universal-validations\} - [ ] **Kết nối cửa hàng**: Đảm bảo bạn đã kết nối Adapty với App Store và/hoặc Google Play: - [ ] [App Store](initial_ios) - [ ] [Google Play](initial-android) - [ ] **Gửi sự kiện gói đăng ký**: Xác nhận rằng thông báo từ máy chủ đã được thiết lập: - [ ] [Thông báo máy chủ App Store](enable-app-store-server-notifications) - [ ] [Thông báo nhà phát triển theo thời gian thực (RTDN)](enable-real-time-developer-notifications-rtdn) - [ ] **Nhận diện hồ sơ người dùng**: Kiểm tra logic nhận diện người dùng và đảm bảo các giao dịch mua được gắn đúng hồ sơ người dùng: - [ ] [Kiểm tra logic nhận diện trong code ứng dụng của bạn có khớp với trường hợp sử dụng không](ios-quickstart-identify) - [ ] [Đảm bảo bạn hiểu logic cha/kế thừa khi chia sẻ quyền truy cập trả phí giữa các hồ sơ người dùng](sharing-paid-access-between-user-accounts) - [ ] **Ưu đãi**: Nếu ứng dụng có ưu đãi cho App Store, hãy đảm bảo bạn đã [thêm In-app purchase key](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers) vào cả trường chính lẫn phần **App Store promotional offers**. - [ ] **Thu thập dữ liệu**: Đảm bảo tuân thủ quyền riêng tư: - [ ] Nếu bạn cần tuân thủ các quy định về quyền riêng tư như GDPR hoặc CCPA, hoặc ứng dụng dành cho trẻ em, hãy kiểm soát việc [bật IDFA và thu thập/chia sẻ IP](sdk-installation-ios#data-policies). - [ ] Nếu ứng dụng sử dụng AppTrackingTransparency, hãy đảm bảo bạn đang [gửi trạng thái ủy quyền cho Adapty](ios-deal-with-att). - [ ] **Nhãn quyền riêng tư**: [Tìm hiểu thêm](apple-app-privacy) về dữ liệu Adapty thu thập và các flag bạn cần thiết lập để kiểm duyệt. ## Kiểm tra giao dịch mua \{#purchase-validations\} --- no_index: true --- import Callout from '../../../components/Callout.astro';
2. Chọn **Product** > **Archive** từ thanh menu phía trên.
3. Chờ quá trình lưu trữ hoàn tất. Cửa sổ **Organizer** sẽ tự động mở. Chọn bản lưu trữ của bạn và nhấp **Distribute App**.
4. Chọn **App Store Connect** làm phương thức phân phối. Làm theo các bước hướng dẫn để hoàn tất việc tải lên.
:::note
Quá trình tải lên có thể thất bại nếu thiếu các tài nguyên bắt buộc, chẳng hạn như biểu tượng ứng dụng hoặc màn hình khởi động. Kiểm tra nhật ký lỗi trong Xcode để biết thêm chi tiết.
:::
### Bước 2. Kiểm tra bản build trong App Store Connect \{#step-2-check-the-build-in-app-store-connect\}
1. Truy cập [App Store Connect](https://appstoreconnect.apple.com) và mở ứng dụng của bạn.
2. Cuộn đến phần **Build**. Đảm bảo rằng bản build bạn vừa tải lên xuất hiện ở đó.
:::note
Có thể mất vài phút để bản build xuất hiện trong App Store Connect sau khi tải lên.
:::
## Gửi ứng dụng và sản phẩm để xét duyệt \{#submit-your-app-and-products-for-review\}
Sau khi bản build xuất hiện trong phần **Build**, hãy đính kèm các gói đăng ký in-app và gửi ứng dụng để Apple xét duyệt.
### Bước 1. Đính kèm sản phẩm vào bản gửi \{#step-1-attach-products-to-the-submission\}
Mỗi gói đăng ký phải có trạng thái **Ready to Submit** trong App Store Connect trước khi bạn có thể đính kèm. Nếu một gói đăng ký vẫn ở trạng thái nháp hoặc thiếu thông tin metadata, nó sẽ không xuất hiện trong danh sách.
1. Trên cùng trang đó, cuộn xuống phần **In-App Purchases and Subscriptions**.
2. Nhấp **Select in-app purchases or subscriptions**.
3. Chọn tất cả sản phẩm bạn muốn đưa vào bản gửi này và nhấp **Done**.
### Bước 2. Gửi để xét duyệt \{#step-2-submit-for-review\}
1. Điền đầy đủ tất cả các trường bắt buộc trên trang (mô tả, ảnh chụp màn hình, từ khóa, v.v.).
2. Trong phần **App Store Version Release**, chọn cách bạn muốn phát hành ứng dụng: tự động, thủ công hoặc theo lịch sau khi được phê duyệt.
3. Nhấp **Add for Review**, sau đó nhấp **Submit to App Review**.
Apple xét duyệt ứng dụng trong vòng 1–2 ngày, tuy nhiên thời gian xét duyệt có thể thay đổi.
## Xác minh ứng dụng trên môi trường production \{#verify-your-app-in-production\}
Sau khi Apple phê duyệt ứng dụng của bạn:
1. Thực hiện một giao dịch mua thực tế (hoặc chờ người dùng đầu tiên của bạn mua hàng).
2. Mở [**Event Feed**](https://app.adapty.io/event-feed) trong Adapty Dashboard và xác nhận rằng các sự kiện giao dịch production xuất hiện.
3. Kiểm tra xem các sự kiện gói đăng ký (gia hạn, hủy) có hoạt động đúng không — điều này phụ thuộc vào việc [thông báo từ máy chủ App Store](enable-app-store-server-notifications) đã được cấu hình.
Nếu sự kiện production không xuất hiện, hãy kiểm tra [cấu hình kết nối App Store](app-store-connection-configuration) của bạn.
## Các bước tiếp theo \{#next-steps\}
Ứng dụng của bạn đã ra mắt. Bắt đầu tăng doanh thu từ gói đăng ký:
- **[A/B testing](ab-tests)**: Thử nghiệm với các paywall khác nhau để tìm ra cách chuyển đổi tốt nhất.
- **[Analytics](charts)**: Theo dõi các chỉ số gói đăng ký như MRR, churn và tỷ lệ chuyển đổi.
- **Tích hợp**: Gửi sự kiện gói đăng ký đến các nền tảng [analytics](analytics-integration) và [attribution](attribution-integration).
---
# File: general
---
---
title: "Cài đặt ứng dụng"
description: "Khám phá các cài đặt và cấu hình chung trong Adapty để sử dụng liền mạch."
---
Bạn có thể điều hướng đến tab General của trang App Settings để quản lý hành vi, giao diện và chia sẻ doanh thu của ứng dụng. Tại đây, bạn có thể tùy chỉnh tên và biểu tượng ứng dụng, quản lý các khóa Adapty SDK và API, thiết lập trạng thái Chương trình Doanh nghiệp Nhỏ, và chọn múi giờ cho analytics và biểu đồ của ứng dụng.
## 1. Chi tiết ứng dụng \{#1-app-details\}
Chọn tên và biểu tượng duy nhất đại diện cho ứng dụng của bạn trong giao diện Adapty. Lưu ý rằng tên và biểu tượng ứng dụng sẽ không ảnh hưởng đến tên và biểu tượng ứng dụng trên App Store hoặc Google Play. Ngoài ra, hãy chắc chắn chọn Danh mục ứng dụng phù hợp phản ánh đúng mục đích và nội dung của ứng dụng. Điều này sẽ giúp người dùng khám phá ứng dụng của bạn và đảm bảo nó xuất hiện trong các danh mục cửa hàng ứng dụng phù hợp.
## 2\. Thành viên Chương trình Doanh nghiệp Nhỏ và Phí Dịch vụ Giảm \{#2-member-of-small-business-program-and-reduced-service-fee\}
Nếu tổ chức của bạn đã đăng ký [Chương trình Doanh nghiệp Nhỏ](app-store-small-business-program) của Apple hoặc [chương trình Phí Dịch vụ Giảm](google-reduced-service-fee) của Google, ứng dụng của bạn sẽ được hưởng mức hoa hồng cửa hàng giảm.
Hãy thông báo cho Adapty nếu ứng dụng của bạn đã đăng ký chương trình hoa hồng giảm. Để đảm bảo tính toán chính xác, hãy chỉ định trạng thái của các chương trình này trong phần "Reduced Store Fee".
Cài đặt phí giảm chỉ áp dụng cho các giao dịch trong tương lai. Hãy thay đổi trạng thái của bạn **trước khi** nó có hiệu lực, và Adapty sẽ điều chỉnh tỷ lệ hoa hồng.
:::warning
* Nếu bạn gia hạn tham gia chương trình phí giảm, hãy **thêm thêm một khoảng thời gian đủ điều kiện**.
* Nếu bạn mất tư cách thành viên chương trình, hãy **thay đổi ngày hết hạn** của khoảng thời gian đủ điều kiện hiện tại.
:::
Các bài viết sau đây khám phá chủ đề này sâu hơn:
* [App Store Small Business Program](app-store-small-business-program)
* [Google Reduced Service Fee](google-reduced-service-fee)
## 3\. Múi giờ báo cáo \{#3-reporting-timezone\}
Chọn múi giờ tương ứng với vị trí bạn đang ở, hoặc nơi analytics và biểu đồ của ứng dụng có liên quan nhất. Chúng tôi khuyến nghị sử dụng cùng múi giờ với tài khoản App Store Connect hoặc Google Play Console để đảm bảo tính nhất quán. Lưu ý rằng cài đặt múi giờ này không ảnh hưởng đến các tích hợp bên thứ ba trong hệ thống Adapty, vốn sử dụng múi giờ UTC.
Bạn có thể truy cập cài đặt múi giờ trong phần Reported timezone của tab General trên trang App Settings. Bạn cũng có thể chọn đặt cùng một múi giờ cho tất cả các ứng dụng trong tài khoản Adapty của mình bằng cách đánh dấu vào ô tương ứng.
## 4\. Định nghĩa lượt cài đặt cho analytics \{#4-installs-definition-for-analytics\}
Chọn điều gì được định nghĩa là một sự kiện cài đặt mới trong analytics:
| Cơ sở | Mô tả |
|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| New device_ids | (Khuyến nghị) Mỗi lần cài đặt ứng dụng từ cửa hàng trên một thiết bị được tính là một lượt cài đặt mới. Điều này bao gồm cả lần cài đặt đầu tiên và cài đặt lại.
Lượt cài đặt được tính theo ID thiết bị và không bị ảnh hưởng bởi xác thực người dùng. 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 không tạo ra thêm sự kiện cài đặt.
Ví dụ: nếu cùng một ứng dụng được cài đặt trên 5 thiết bị khác nhau, bạn sẽ thấy 5 lượt cài đặt trong analytics.
| | New customer_user_ids |Tùy chọn này dành cho các ứng dụng
Đối với người dùng đã đăng nhập, chỉ lần cài đặt đầu tiên gắn với customer user ID mới được tính là một lượt cài đặt. Cài đặt trên các thiết bị bổ sung không được tính là lượt cài đặt mới.
Người dùng ẩn danh (người dùng chưa đăng nhập) không được tính trong analytics.
Cài đặt lại ứng dụng hoặc đăng nhập lại không tạo thêm lượt cài đặt.
Các cửa hàng ứng dụng và nền tảng attribution (như App Store Connect, Google Play Console và AppsFlyer) sử dụng phương pháp dựa trên thiết bị để đếm lượt cài đặt. Nếu bạn đếm lượt cài đặt theo customer user ID trong Adapty, số lượt cài đặt có thể khác với các dịch vụ bên ngoài này.
⚠️ Nếu bạn không xác định người dùng trong Adapty, sẽ không có lượt cài đặt nào được tính khi bật tùy chọn này.
| | New profiles in Adapty | (Legacy) Mỗi lần cài đặt, cài đặt lại ứng dụng và các hồ sơ người dùng ẩn danh được tạo ra trong quá trình đăng xuất đều được tính là lượt cài đặt mới. | Lưu ý rằng tùy chọn này chỉ ảnh hưởng đến trang [**Analytics**](https://app.adapty.io/analytics) và không tác động đến trang [**Overview**](https://app.adapty.io/overview), nơi bạn có thể cấu hình chế độ xem riêng. ## 5. Logic tăng giá trên App Store \{#5-app-store-price-increase-logic\} Để duy trì dữ liệu chính xác và tránh sự khác biệt giữa analytics Adapty và kết quả từ App Store Connect, điều quan trọng là phải chọn tùy chọn phù hợp khi điều chỉnh các cấu hình liên quan đến tăng giá trong App Store Connect. Vì vậy, bạn có thể chọn logic sẽ được áp dụng cho việc tăng giá gói đăng ký trong Adapty:
- **Giá gói đăng ký cho người dùng hiện tại được giữ nguyên:** Bằng cách chọn tùy chọn này, giá hiện tại sẽ được giữ lại cho những người đăng ký hiện có, ngay cả khi bạn thay đổi giá trong App Store Connect. Điều này có nghĩa là người đăng ký hiện tại sẽ tiếp tục bị tính phí theo giá gói đăng ký ban đầu của họ.
- **Khi giá gói đăng ký thay đổi trong App Store Connect, nó sẽ thay đổi cho cả người đăng ký hiện tại:** Nếu bạn chọn tùy chọn này, mọi thay đổi về giá được thực hiện trong App Store Connect cũng sẽ được áp dụng cho người đăng ký hiện có. Điều này có nghĩa là người đăng ký hiện tại sẽ bị tính phí theo giá mới phản ánh mức giá được cập nhật trong App Store Connect.
:::warning
Điều quan trọng cần lưu ý là tùy chọn được chọn không chỉ ảnh hưởng đến analytics trong Adapty mà còn tác động đến các tích hợp và hành vi xử lý giao dịch tổng thể.
:::
Hãy đảm bảo rằng bạn chọn tùy chọn phù hợp với cách tiếp cận mong muốn của mình khi xử lý giá gói đăng ký cho người đăng ký hiện tại. Điều này sẽ giúp duy trì dữ liệu chính xác và đồng bộ hóa giữa analytics Adapty và kết quả thu được từ App Store Connect.
## 6. Chia sẻ mức độ truy cập có trả phí giữa các tài khoản người dùng \{#6-sharing-paid-access-between-user-accounts\}
:::link
Bài viết chính: [Chia sẻ mức độ truy cập có trả phí giữa các tài khoản người dùng](sharing-paid-access-between-user-accounts)
:::
Cài đặt **Sharing paid access between user accounts** xác định Adapty sẽ làm gì khi có nhiều hơn một [hồ sơ người dùng](identifying-users) cố gắng truy cập cùng một giao dịch mua. Bạn có thể chỉ định cài đặt chia sẻ quyền truy cập riêng biệt cho [môi trường sandbox](test-purchases-in-sandbox).
---
no_index: true
---
**Enabled (mặc định)**
Người dùng đã xác định (những người có [Customer User ID](identifying-users#set-customer-user-id-on-configuration)) có thể chia sẻ cùng một [mức độ truy cập](access-level) do Adapty cung cấp nếu thiết bị của họ đăng nhập vào cùng một Apple/Google ID. Điều này hữu ích khi người dùng cài lại ứng dụng và đăng nhập bằng email khác — họ vẫn có thể truy cập vào giao dịch mua trước đó. Với tùy chọn này, nhiều người dùng đã xác định có thể dùng chung một mức độ truy cập.
Dù mức độ truy cập được chia sẻ, tất cả các giao dịch trong quá khứ và tương lai vẫn được ghi lại dưới dạng sự kiện trong Customer User ID gốc để đảm bảo tính nhất quán của dữ liệu phân tích và lưu giữ toàn bộ lịch sử giao dịch — bao gồm thời gian dùng thử, mua gói đăng ký, gia hạn, v.v., đều được liên kết với cùng một hồ sơ người dùng.
**Transfer access to new user**
Người dùng đã xác định vẫn có thể tiếp tục truy cập [mức độ truy cập](access-level) do Adapty cung cấp, ngay cả khi họ đăng nhập bằng [Customer User ID](identifying-users#set-customer-user-id-on-configuration) khác hoặc cài lại ứng dụng, miễn là thiết bị đăng nhập vào cùng một Apple/Google ID.
Khác với tùy chọn trước, Adapty sẽ chuyển giao dịch mua giữa các người dùng đã xác định. Điều này đảm bảo nội dung đã mua vẫn khả dụng, nhưng chỉ một người dùng có thể truy cập tại một thời điểm. Ví dụ: nếu UserA mua gói đăng ký và UserB đăng nhập trên cùng thiết bị đó và khôi phục giao dịch, UserB sẽ được cấp quyền truy cập gói đăng ký đó, còn UserA sẽ bị thu hồi.
Nếu một trong hai người dùng (mới hoặc cũ) chưa được xác định, mức độ truy cập vẫn sẽ được chia sẻ giữa các hồ sơ người dùng đó trong Adapty.
Dù mức độ truy cập được chuyển giao, tất cả các giao dịch trong quá khứ và tương lai vẫn được ghi lại dưới dạng sự kiện trong Customer User ID gốc để đảm bảo tính nhất quán của dữ liệu phân tích và lưu giữ toàn bộ lịch sử giao dịch — bao gồm thời gian dùng thử, mua gói đăng ký, gia hạn, v.v., đều được liên kết với cùng một hồ sơ người dùng.
Sau khi chuyển sang **Transfer access to new user**, mức độ truy cập sẽ không được chuyển ngay lập tức giữa các hồ sơ người dùng. Quá trình chuyển giao cho từng mức độ truy cập cụ thể chỉ được kích hoạt khi Adapty nhận được sự kiện từ cửa hàng, chẳng hạn như gia hạn gói đăng ký, khôi phục, hoặc khi xác thực giao dịch.
**Disabled**
Hồ sơ người dùng đã xác định đầu tiên được cấp mức độ truy cập sẽ giữ nó mãi mãi. Đây là lựa chọn tốt nhất nếu logic nghiệp vụ của bạn yêu cầu giao dịch mua phải được gắn với một Customer User ID duy nhất.
Lưu ý rằng mức độ truy cập vẫn được chia sẻ giữa các người dùng ẩn danh.
Bạn có thể "gỡ liên kết" giao dịch mua bằng cách [xóa hồ sơ người dùng của chủ sở hữu](https://adapty.io/docs/vi/api-adapty/operations/deleteProfile). Sau khi xóa, mức độ truy cập sẽ khả dụng cho hồ sơ người dùng đầu tiên yêu cầu nó, dù là ẩn danh hay đã xác định.
Việc tắt chia sẻ chỉ ảnh hưởng đến người dùng mới. Các gói đăng ký đã được chia sẻ giữa người dùng sẽ tiếp tục được chia sẻ ngay cả sau khi tắt tùy chọn này.
:::warning
Apple và Google yêu cầu in-app purchase phải được chia sẻ hoặc chuyển giao giữa các người dùng vì họ dựa vào Apple/Google ID để liên kết giao dịch mua. Nếu không có chia sẻ, việc khôi phục giao dịch mua có thể không hoạt động sau khi cài lại ứng dụng.
Tắt chia sẻ có thể khiến người dùng không thể lấy lại quyền truy cập sau khi đăng nhập.
Chúng tôi khuyến nghị chỉ tắt chia sẻ nếu người dùng của bạn **bắt buộc phải đăng nhập** trước khi thực hiện giao dịch mua. Nếu không, một người dùng đã xác định có thể mua gói đăng ký, đăng nhập vào tài khoản khác và mất quyền truy cập vĩnh viễn.
:::
### Tôi nên chọn cài đặt nào? \{#which-setting-should-i-choose\}
| Ứng dụng của tôi... | Tùy chọn nên chọn |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| Không có hệ thống đăng nhập và chỉ sử dụng ID hồ sơ người dùng ẩn danh của Adapty. | Dùng tùy chọn mặc định, vì mức độ truy cập luôn được chia sẻ giữa các ID hồ sơ người dùng ẩn danh cho cả ba tùy chọn. |
| Có hệ thống đăng nhập tùy chọn và cho phép khách hàng mua trước khi tạo tài khoản. | Chọn **Transfer access to new user** để đảm bảo những khách hàng mua khi chưa có tài khoản vẫn có thể khôi phục giao dịch sau này. |
| Yêu cầu khách hàng tạo tài khoản trước khi mua, nhưng cho phép giao dịch mua được liên kết với nhiều Customer User ID. | Chọn **Transfer access to new user** để đảm bảo chỉ một Customer User ID có quyền truy cập tại một thời điểm, đồng thời vẫn cho phép người dùng đăng nhập bằng Customer User ID khác mà không mất quyền truy cập đã trả phí. |
| Yêu cầu khách hàng tạo tài khoản trước khi mua, với quy tắc nghiêm ngặt ràng buộc giao dịch mua với một Customer User ID duy nhất. | Chọn **Disabled** để đảm bảo giao dịch không bao giờ được chuyển giao giữa các tài khoản. |
## 7. Khóa SDK và API \{#7-sdk-and-api-keys\}
Sử dụng Public SDK key để tích hợp Adapty SDK vào ứng dụng của bạn, và Secret Key để truy cập Server API của Adapty. Bạn có thể tạo khóa mới hoặc thu hồi khóa hiện có khi cần. Để tạo token cho Developer CLI, hãy vào **Settings → Developer API**. Xem [Authentication](developer-cli-authentication).
## 8. Thiết bị kiểm thử \{#8-test-devices\}
Chỉ định các thiết bị sẽ được sử dụng để kiểm thử nhằm đảm bảo chúng nhận được cập nhật tức thì cho các thay đổi về paywall hoặc placement, bỏ qua bất kỳ độ trễ bộ nhớ đệm nào. Để biết thêm thông tin, xem [Testing devices](test-devices).
## 9. Tính nhất quán biến thể xuyên placement \{#9-cross-placement-variation-stickiness\}
Xác định thời gian bao lâu sau khi hoàn thành một test, người dùng vẫn được phục vụ với các biến thể trong test đó. Điều này ảnh hưởng đến độ chính xác của analytics và trải nghiệm người dùng — vì việc hiển thị cho người dùng một ưu đãi khác với những gì họ đã thấy trước đó có thể ảnh hưởng đến quyết định mua hàng của họ.
Thời gian nhất quán tối đa và mặc định là 90 ngày.
:::warning
Hãy lưu ý những điều sau:
- Thay đổi cài đặt này sẽ ảnh hưởng đến tất cả những người dùng đã nhận được một biến thể trước đó. Họ sẽ ngay lập tức đủ điều kiện cho một paywall mới khi thấy một placement, điều này có thể làm hỏng kết quả của các A/B test đang chạy.
- Nếu thời gian nhất quán đã hết cho một người dùng, họ có thể được phục vụ với một paywall hoặc A/B test mới. Tuy nhiên, ngay cả khi đó, họ sẽ không thể tham gia vào bất kỳ cross-placement test nào khác.
:::
## 10. Xóa ứng dụng \{#10-delete-the-app\}
Nếu bạn không còn cần một ứng dụng nữa, bạn có thể xóa nó khỏi Adapty.
:::warning
Xin lưu ý rằng hành động này không thể hoàn tác, và bạn sẽ không thể khôi phục ứng dụng hoặc dữ liệu của nó.
:::
---
# File: ios-settings
---
---
title: "Thông tin xác thực Apple App Store"
description: "Cấu hình cài đặt iOS trong Adapty để quản lý gói đăng ký liền mạch."
---
Để cấu hình thông tin xác thực App Store và đảm bảo SDK iOS của Adapty hoạt động tối ưu, hãy truy cập tab [iOS SDK](https://app.adapty.io/settings/ios-sdk) trong trang App Settings của Adapty Dashboard. Sau đó, cấu hình các thông số sau:
| Trường | Mô tả |
|----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Bundle ID** | [Bundle ID](app-store-connection-configuration#step-1-provide-bundle-id-and-apple-app-id) của ứng dụng. |
| **In-app purchase API (StoreKit 2)** | [Khóa](app-store-connection-configuration#step-2-provide-issuer-id-and-key-id) để xác thực bảo mật và kiểm tra lịch sử giao dịch in-app purchase. |
| **App Store Server Notifications** | URL dùng để bật [thông báo server2server](enable-app-store-server-notifications) từ App Store nhằm theo dõi và phản hồi các thay đổi trạng thái gói đăng ký của người dùng. |
| **App Store Promotional Offers** | Khóa gói đăng ký dùng để tạo [ưu đãi](generate-in-app-purchase-key) trong Adapty cho các sản phẩm cụ thể. |
| **Apple app ID** | ID ứng dụng trên App Store. Để tìm, hãy mở trang ứng dụng trong App Store Connect, chọn trang **App Information** từ menu bên trái và sao chép **Apple ID**. |
| **App Store Connect shared secret (LEGACY)** | **Khóa cũ dành cho Adapty SDK trước phiên bản v2.9.0**
[Khóa](app-store-connection-configuration#step-5-enter-app-store-shared-secret) dùng để xác thực biên lai và ngăn chặn gian lận trong ứng dụng.
| --- # File: google-play-store-connection-configuration --- --- title: "Cấu hình tích hợp Google Play Store" description: "Cấu hình kết nối Google Play Store trong Adapty để xử lý in-app purchase suôn sẻ." --- Phần này mô tả quy trình tích hợp ứng dụng di động của bạn được bán qua Google Play với Adapty. Bạn cần nhập dữ liệu cấu hình ứng dụng từ Play Store vào Adapty Dashboard. Bước này rất quan trọng để xác thực các giao dịch mua và nhận cập nhật gói đăng ký từ Play Store trong Adapty. Bạn có thể hoàn tất quá trình này trong lần onboarding đầu tiên hoặc thay đổi sau trong **App Settings** của Adapty Dashboard. :::danger Chỉ được phép thay đổi cấu hình trước khi bạn phát hành ứng dụng di động tích hợp Adapty paywall. Việc thay đổi sau khi phát hành sẽ phá vỡ tích hợp và các paywall sẽ ngừng hiển thị trong ứng dụng di động của bạn. ::: ## Bước 1. Cung cấp Package name \{#step-1-provide-package-name\} Package name là định danh duy nhất của ứng dụng trên Google Play Store. Đây là yêu cầu bắt buộc cho các chức năng cơ bản của Adapty, chẳng hạn như xử lý gói đăng ký. 1. Mở [Google Play Developer Console](https://play.google.com/console/u/0/developers). 2. Chọn ứng dụng mà bạn cần lấy ID. Cửa sổ **Dashboard** sẽ mở ra.
3. Tìm product ID bên dưới tên ứng dụng và sao chép nó.
4. Mở [**App settings**](https://app.adapty.io/settings/android-sdk) từ menu trên cùng của Adapty.
5. Trong tab **Android SDK** của cửa sổ **App settings**, dán **Package name** vừa sao chép vào.
## Bước 2. Tải lên tệp khóa tài khoản \{#step-2-upload-the-account-key-file\}
1. Tải lên tệp khóa riêng tư của tài khoản dịch vụ ở định dạng JSON mà bạn đã tạo ở bước [Tạo tệp khóa tài khoản dịch vụ](create-service-account) vào khu vực **Service account key file**.
Đừng quên nhấn nút **Save** để xác nhận các thay đổi.
**Tiếp theo**
- [Bật Real-time developer notifications (RTDN) trong Google Play Console](enable-real-time-developer-notifications-rtdn)
---
# File: enable-real-time-developer-notifications-rtdn
---
---
title: "Bật thông báo nhà phát triển theo thời gian thực (RTDN) trong Google Play Console"
description: "Luôn được thông báo về các sự kiện quan trọng và đảm bảo độ chính xác của dữ liệu bằng cách bật Thông báo nhà phát triển theo thời gian thực (RTDN) trong Google Play Console cho Adapty. Tìm hiểu cách thiết lập RTDN để nhận cập nhật tức thì về hoàn tiền và các sự kiện quan trọng khác từ Play Store"
---
Việc thiết lập thông báo nhà phát triển theo thời gian thực (RTDN) rất quan trọng để đảm bảo độ chính xác của dữ liệu, vì nó cho phép bạn nhận cập nhật tức thì từ Play Store, bao gồm thông tin về hoàn tiền và các sự kiện khác.
## Bật thông báo \{#enable-notifications\}
1. Đảm bảo bạn đã bật **Google Cloud Pub/Sub**. Mở [liên kết này](https://console.cloud.google.com/flows/enableapi?apiid=pubsub) và chọn dự án ứng dụng của bạn. Nếu chưa bật **Google Cloud Pub/Sub**, bạn phải thực hiện tại đây.
2. Vào [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) từ menu trên cùng của Adapty và sao chép nội dung trong trường **Enable Pub/Sub API** bên cạnh tiêu đề **Google Play RTDN topic name**.
:::note Nếu nội dung trong trường **Enable Pub/Sub API** có định dạng sai (định dạng đúng bắt đầu bằng `projects/...`), hãy tham khảo phần [Sửa định dạng sai trong trường Enable Pub/Sub API](enable-real-time-developer-notifications-rtdn#fixing-incorrect-format-in-enable-pubsub-api-field) để được hỗ trợ. ::: 3. Mở [Google Play Console](https://play.google.com/console/), chọn ứng dụng của bạn, rồi vào **Monetize with Play** -> **Monetization setup**. Trong phần **Google Play Billing**, chọn hộp kiểm **Enable real-time notifications**. 4. Dán nội dung của trường **Enable Pub/Sub API** mà bạn đã sao chép trong **App Settings** của Adapty vào trường **Topic name**. 5. Nhấp **Save changes** trong Google Play Console.
## Kiểm tra thông báo \{#test-notifications\}
Để kiểm tra xem bạn đã đăng ký nhận thông báo nhà phát triển theo thời gian thực thành công chưa:
1. Lưu các thay đổi trong cài đặt Google Play Console.
2. Bên dưới **Topic name** trong Google Play Console, nhấp **Send test notification**.
3. Vào [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) trong Adapty. Nếu thông báo kiểm tra đã được gửi, bạn sẽ thấy trạng thái của nó phía trên tên topic.
## Sửa định dạng sai trong trường Enable Pub/Sub API \{#fixing-incorrect-format-in-enable-pubsub-api-field\}
Nếu nội dung trong trường **Enable Pub/Sub API** có định dạng sai (định dạng đúng bắt đầu bằng `projects/...`), hãy làm theo các bước sau để khắc phục sự cố:
### 1. Xác minh việc bật API và phân quyền \{#1-verify-api-enablement-and-permissions\}
Hãy đảm bảo cẩn thận rằng tất cả các API cần thiết đã được bật và quyền đã được cấp đúng cho service account. Dù bạn đã hoàn thành các bước này rồi, vẫn nên thực hiện lại để chắc chắn không bỏ sót bước nào. Lặp lại các bước trong các phần sau:
1. [Bật Developer APIs trong Google Play Console](enabling-of-devepoler-api)
2. [Tạo service account trong Google Cloud Console](create-service-account)
3. [Cấp quyền cho service account trong Google Play Console](grant-permissions-to-service-account)
4. [Tạo file khóa service account trong Google Play Console](create-service-account-key-file)
5. [Cấu hình tích hợp Google Play Store](google-play-store-connection-configuration)
### 2. Điều chỉnh chính sách Domain \{#2-adjust-domain-policies\}
Thay đổi chính sách **Domain restricted contacts** và **Domain restricted sharing**:
1. Mở [Google Cloud Console](https://console.cloud.google.com/) và chọn dự án mà bạn đã tạo service account để quản lý ứng dụng.
2. Trong phần **Quick Access**, chọn **IAM & Admin**.
3. Ở khung bên trái, chọn **Organization Policies**.
4. Tìm chính sách **Domain restricted contacts**.
5. Nhấp vào nút dấu ba chấm trong cột **Actions** và chọn **Edit policy**.
6. Trong cửa sổ chỉnh sửa chính sách:
1. Dưới **Policy source**, chọn radio button **Override parent's policy**.
2. Dưới **Policy enforcement**, chọn radio button **Replace**.
3. Dưới **Rules**, nhấp nút **ADD A RULE**.
4. Dưới **New rule** -> **Policy values**, chọn **Allow All**.
5. Nhấp **SET POLICY**.
7. Lặp lại các bước 4-6 cho chính sách **Domain restricted sharing**.
Cuối cùng, tạo lại nội dung của trường **Enable Pub/Sub API** bên cạnh tiêu đề **Google Play RTDN topic name**. Trường này sẽ có định dạng đúng.
Hãy nhớ chuyển **Policy source** trở lại **Inherit parent's policy** cho các chính sách đã cập nhật sau khi bạn đã bật thành công Thông báo nhà phát triển theo thời gian thực (RTDN).
## Chuyển tiếp sự kiện thô \{#raw-events-forwarding\}
Đôi khi bạn vẫn muốn nhận các sự kiện S2S thô từ Google. Để tiếp tục nhận chúng khi sử dụng Adapty, chỉ cần thêm endpoint của bạn vào trường **URL for forwarding raw Google events**, và chúng tôi sẽ chuyển tiếp các sự kiện thô nguyên bản từ Google.
---
**Tiếp theo**
Thiết lập Adapty SDK cho:
- [Android](sdk-installation-android)
- [React Native](sdk-installation-reactnative)
- [Flutter](sdk-installation-flutter)
- [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform)
- [Unity](sdk-installation-unity)
---
# File: apple-search-ads
---
---
title: "Apple Ads"
description: "Tích hợp Apple Ads với Adapty để tối ưu hóa chuyển đổi gói đăng ký."
---
:::important
Tích hợp Apple Ads trong **App settings** chỉ dùng cho analytics cơ bản và cho các tích hợp SplitMetrics Acquire và Asapty.
[Apple Ads Manager](adapty-ads-manager) sử dụng kết nối riêng biệt. Kết nối tài khoản Apple Ads của bạn trong [cài đặt Apple Ads Manager](adapty-ads-manager-get-started).
:::
Adapty có thể giúp bạn lấy dữ liệu attribution từ Apple Ads và phân tích các chỉ số theo chiến dịch và từ khóa. Adapty tự động thu thập dữ liệu attribution cho Apple Ads thông qua SDK và AdServices Framework.
Sau khi thiết lập tích hợp Apple Ads, Adapty sẽ bắt đầu nhận dữ liệu attribution từ Apple Ads. Bạn có thể dễ dàng xem dữ liệu này trên trang hồ sơ người dùng.
## Thiết lập tích hợp \{#set-up-integration\}
### Kết nối Adapty với AdServices framework \{#connect-adapty-to-the-adservices-framework\}
Apple Ads qua [AdServices](https://developer.apple.com/documentation/adservices) yêu cầu một số cấu hình trong Adapty Dashboard, đồng thời bạn cũng cần bật tính năng này phía ứng dụng. Để thiết lập Apple Ads bằng AdServices framework qua Adapty, làm theo các bước sau:
#### Bước 1: Lấy public key \{#step-1-obtain-public-key\}
Trong Adapty Dashboard, truy cập [Settings -> Apple Ads.](https://app.adapty.io/settings/apple-search-ads)
Tìm public key đã được tạo sẵn (Adapty cung cấp cặp khóa cho bạn) và sao chép nó.
:::note
Nếu bạn đang dùng dịch vụ khác hoặc giải pháp riêng cho attribution Apple Ads, bạn có thể tải lên private key của mình.
:::
#### Bước 2: Cấu hình quản lý người dùng trên Apple Ads \{#step-2-configure-user-management-on-apple-ads\}
Trong [tài khoản Apple Ads](https://ads.apple.com/app-store) của bạn, vào trang **Settings > User Management**. Để Adapty có thể lấy dữ liệu attribution, bạn cần mời một tài khoản Apple ID khác và cấp quyền truy cập API Account Manager. Bạn có thể dùng bất kỳ tài khoản nào bạn có quyền truy cập hoặc tạo một tài khoản mới riêng cho mục đích này. Điều quan trọng là bạn phải có thể đăng nhập vào Apple Ads bằng Apple ID đó.
#### Bước 3: Tạo thông tin xác thực API \{#step-3-generate-api-credentials\}
Tiếp theo, đăng nhập vào tài khoản vừa thêm trong Apple Ads. Vào Settings -> API trong giao diện Apple Ads. Dán public key đã sao chép vào trường được chỉ định. Tạo thông tin xác thực API mới.
#### Bước 4: Cấu hình Adapty với thông tin xác thực Apple Ads \{#step-4-configure-adapty-with-apple-ads-credentials\}
Sao chép các trường Client ID, Team ID và Key ID từ cài đặt Apple Ads. Trong Adapty Dashboard, dán các thông tin này vào các trường tương ứng.
### Kết nối ứng dụng với mạng AdServices \{#connect-your-app-to-the-adservices-network\}
Sau khi hoàn tất [thiết lập AdServices framework](#connect-the-adservices-framework), Adapty sẽ tự động bắt đầu thu thập dữ liệu attribution Apple Search Ad. Bạn không cần thêm bất kỳ đoạn code SDK nào.
Đối với ứng dụng iOS, dữ liệu attribution này sẽ **luôn** được ưu tiên hơn dữ liệu từ các nguồn khác. Nếu không muốn hành vi này, hãy *tắt* attribution ASA theo hướng dẫn bên dưới.
## Tắt tích hợp \{#disable-integration\}
Để tắt attribution Apple Search Ads, mở tab [**App Settings** -> **Apple Search Ads**](https://app.adapty.io/settings/apple-search-ads) và tắt công tắc **Receive Apple Search Ads attribution**.
:::warning
Lưu ý rằng việc tắt tính năng này sẽ hoàn toàn dừng việc nhận analytics ASA. Kết quả là, ASA sẽ không còn được sử dụng trong analytics hoặc gửi đến các tích hợp. Ngoài ra, SplitMetrics Acquire và Asapty sẽ ngừng hoạt động vì chúng phụ thuộc vào attribution ASA để hoạt động đúng.
Dữ liệu attribution nhận được trước thay đổi này sẽ không bị ảnh hưởng.
:::
## Tải lên key của riêng bạn \{#uploading-your-own-keys\}
:::note
Tùy chọn
Các bước này không bắt buộc cho attribution Apple Ads, chỉ cần thiết khi làm việc với các dịch vụ khác như Asapty hoặc giải pháp của riêng bạn.
:::
Bạn có thể dùng cặp khóa public-private của riêng mình nếu đang sử dụng dịch vụ khác hoặc giải pháp riêng cho attribution ASA.
### Bước 1 \{#step-1\}
Tạo private key trong Terminal
```text showLineNumbers title="Text"
openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem
```
Tải lên trong Adapty Settings -> Apple Ads (nút Upload private key)
### Bước 2 \{#step-2\}
Tạo public key trong Terminal
```text showLineNumbers title="Text"
openssl ec -in private-key.pem -pubout -out public-key.pem
```
Bạn có thể dùng public key này trong cài đặt Apple Ads của tài khoản có vai trò API Account Manager. Vì vậy, bạn có thể sử dụng các giá trị Client ID, Team ID và Key ID đã tạo cho Adapty và các dịch vụ khác.
---
# File: account
---
---
title: "Thông tin tài khoản & Thanh toán"
description: "Quản lý tài khoản Adapty của bạn và tối ưu cài đặt để theo dõi gói đăng ký tốt hơn."
---
Trang **Account** cho phép bạn quản lý hồ sơ người dùng, thành viên nhóm và thanh toán.
Trang này có ba tab:
- [Chung](#general-settings)
- [Gói đăng ký & Thanh toán](#billing-info)
- [Thành viên](#members)
Để truy cập cài đặt tài khoản, nhấp vào **Account** ở góc trên bên phải hoặc truy cập [app.adapty.io/account](https://app.adapty.io/account).
## Cài đặt chung \{#general-settings\}
Tab General chứa hồ sơ người dùng, cài đặt tài khoản, tùy chọn hiển thị và cấu hình báo cáo.
- **Profile**: Nhập họ, tên và tên công ty. Tên công ty có thể dài tối đa 256 ký tự.
- **Account settings**: Xem địa chỉ email đã đăng ký và thay đổi mật khẩu.
- **Date & Time formats**: Chọn cách hiển thị ngày và giờ trong Adapty:
- **American format**: January 31, 2022 và giờ 12 tiếng (AM/PM)
- **European format**: 31 January, 2022 và giờ 24 tiếng (16:00)
- **Email reports**: Thiết lập báo cáo hàng ngày, hàng tuần hoặc hàng tháng cho một hoặc tất cả ứng dụng của bạn. Nhận báo cáo tổng hợp cho tất cả ứng dụng cùng lúc, hoặc nhận báo cáo chi tiết cho từng ứng dụng được chọn.
## Gói đăng ký & Thanh toán \{#billing-info\}
Tab **Subscription & Billing** cho phép bạn quản lý thông tin thanh toán và quyền truy cập tính năng:
- Thêm hoặc cập nhật thông tin thanh toán
- Xem lại thông tin hóa đơn
- Mua thêm các tính năng trả phí
Tìm hiểu thêm về [tính năng và bảng giá](https://adapty.io/pricing).
## Thành viên \{#members\}
Bạn có thể quản lý thành viên nhóm trong cài đặt tài khoản. Để thêm thành viên, hãy mời họ qua email và gán vai trò cho họ.
Đọc thêm về cách quản lý thành viên nhóm và quyền truy cập của họ [tại đây](members-settings).
---
# File: members-settings
---
---
title: "Thành viên"
description: "Quản lý cài đặt và quyền hạn của thành viên trên dashboard của Adapty."
---
:::note
Trang này nói về các thành viên trên Adapty Dashboard
Nếu bạn muốn cấp các mức độ truy cập khác nhau cho người dùng ứng dụng, hãy xem [Mức độ truy cập](access-level).
:::
Hệ thống thành viên trên Adapty Dashboard cho phép bạn cấp các mức quyền truy cập khác nhau vào Adapty và chỉ định ứng dụng cho từng thành viên.
## Vai trò \{#roles\}
Các vai trò sau đây có sẵn cho thành viên trên Adapty Dashboard:
| Vai trò | Quyền truy cập Billing | Thêm thành viên mới | Thay đổi bất cứ điều gì | Quyền truy cập tất cả phần |
|-------------|------------------------|---------------------|-------------------------|----------------------------|
| Owner | ✅ | ✅ | ✅ | ✅ |
| Admin | ❌ | ✅ | ✅ | ✅ |
| Developer | ❌ | ❌ | ✅ | ❌ |
| Viewer | ❌ | ❌ | ❌ | ✅ |
| Support | ❌ | ❌ | ❌ | ❌ |
| ASA manager | ❌ | ❌ | ❌ | ❌ |
- **Owner:** Owner là người tạo tài khoản Adapty ban đầu và có mức quyền truy cập cũng như kiểm soát cao nhất. Owner có toàn quyền truy cập vào phần billing của Adapty, cho phép quản lý thông tin thanh toán và các gói đăng ký. Ngoài ra, chỉ Owner và Admin mới có thể chỉ định quyền truy cập ứng dụng cho thành viên mới. Mỗi tài khoản Adapty chỉ có thể có một Owner.
- **Admin:** Thành viên có vai trò Admin có toàn quyền truy cập vào các ứng dụng được chỉ định. Họ có thể thực hiện nhiều tác vụ quản lý, bao gồm tạo và chỉnh sửa paywall, thực hiện A/B test, phân tích analytics và quản lý thành viên trong các ứng dụng đó.
- **Developer:** Thành viên có vai trò Developer có toàn quyền truy cập vào tất cả các thực thể, ngoại trừ analytics và quản lý thành viên tài khoản. Họ không thể truy cập bất kỳ cài đặt billing nào. Vai trò này dành cho những người thiết lập paywall, A/B test và các thực thể khác, đồng thời tích hợp Adapty vào ứng dụng, nhưng không cần xem dữ liệu tài chính.
- **Viewer:** Thành viên có vai trò Viewer có quyền truy cập chỉ đọc vào các ứng dụng được chỉ định. Họ có thể xem thông tin nhưng không thể tạo hoặc chỉnh sửa paywall, A/B test và các tính năng khác, mời người dùng mới, tạo ứng dụng mới hay thay đổi cài đặt ứng dụng.
- **Support:** Thành viên có vai trò Support chỉ có quyền truy cập vào hồ sơ người dùng trong các ứng dụng được chỉ định. Tuy nhiên, họ không thể thực hiện các thao tác như thêm thành viên mới hoặc truy cập các phần khác của Adapty. Vai trò này đặc biệt phù hợp với đội ngũ hỗ trợ hoặc những cá nhân cần hỗ trợ khách hàng với các vấn đề liên quan đến gói đăng ký hoặc khắc phục sự cố.
- **ASA manager:** Thành viên có vai trò ASA manager chỉ có quyền truy cập vào dashboard [Apple Ads Manager](adapty-ads-manager).
## Thêm thành viên \{#add-a-member\}
Trong Adapty, bạn có thể mời tối đa 256 thành viên trong nhóm. Việc thêm thành viên mới hoàn toàn miễn phí.
:::note
Bạn chỉ có thể mời các địa chỉ email chưa được đăng ký trong Adapty. Nếu đồng nghiệp của bạn đã có tài khoản riêng, hãy mời một địa chỉ email khác hoặc liên hệ bộ phận hỗ trợ Adapty để xóa tài khoản hiện tại của họ.
:::
Để thêm thành viên vào nhóm:
1. Nhấp vào **Account** ở góc trên bên phải và mở tab **Members**.
2. Nhấp vào **Invite member**.
3. Nhập địa chỉ email của thành viên.
4. Chọn một [vai trò](#roles) từ danh sách.
5. Chọn các ứng dụng để cấp quyền truy cập.
6. (Tùy chọn) Bật **Always allow access to new apps** để tự động cấp quyền truy cập vào các ứng dụng trong tương lai.
7. Nhấp vào **Save**.
## Chuyển giao quyền sở hữu tài khoản \{#transfer-account-ownership\}
Nếu bạn cần chuyển giao toàn bộ **quyền sở hữu tài khoản**, hãy liên hệ đội ngũ hỗ trợ của chúng tôi tại [support@adapty.io](mailto:support@adapty.io).
Nếu bạn cần chuyển giao **quyền sở hữu ứng dụng**, hãy đọc [hướng dẫn chuyên biệt](transfer-apps) để biết thêm thông tin.
---
# File: set-up-app-store-connect
---
---
title: "Thiết lập App Store Connect"
description: "Hướng dẫn cho nhà phát triển lần đầu đăng ký Apple Developer Program và thiết lập App Store Connect để bán in-app purchase."
---
Nếu bạn đang **xây dựng ứng dụng iOS đầu tiên**, bạn cần thiết lập tài khoản Apple Developer và App Store Connect trước khi tích hợp Adapty.
:::note
Nếu bạn đã có tài khoản Apple Developer và ứng dụng đã được đăng ký trong App Store Connect, bạn có thể bỏ qua hướng dẫn này và chuyển thẳng đến [Tích hợp ban đầu với App Store](initial_ios).
:::
## Bước 1. Đăng ký Apple Developer Program \{#step-1-enroll-in-apple-developer-program\}
Để phân phối ứng dụng trên App Store và bán in-app purchase, bạn phải tham gia [Apple Developer Program](https://developer.apple.com/programs/).
### Chọn loại đăng ký \{#choose-enrollment-type\}
Apple cung cấp hai loại đăng ký:
| | Cá nhân | Tổ chức |
|----------------------------------|--------------------|-----------------------------------|
| **Dành cho** | Nhà phát triển độc lập | Công ty, nhóm, tổ chức phi lợi nhuận |
| **Yêu cầu D-U-N-S Number** | Không | Có |
| **Ứng dụng được đăng tải dưới** | Tên cá nhân của bạn | Tên tổ chức của bạn |
| **Quản lý nhóm** | Không có | Có |
:::tip
Nếu bạn đăng ký với tư cách tổ chức, bạn cần có **D-U-N-S Number** — mã định danh doanh nghiệp gồm chín chữ số do Dun & Bradstreet cấp. Bạn có thể [kiểm tra xem tổ chức của mình đã có chưa](https://developer.apple.com/enroll/duns-lookup/) hoặc đăng ký mới — đường link ở cuối trang tra cứu. Việc nhận D-U-N-S Number có thể mất đến 5 ngày làm việc.
:::
### Đăng ký \{#enroll\}
1. Truy cập [trang đăng ký Apple Developer Program](https://developer.apple.com/programs/enroll/).
2. Đăng nhập bằng Apple ID của bạn. Nếu chưa có, hãy tạo tài khoản trước.
3. Làm theo các bước phù hợp với loại đăng ký của bạn (cá nhân hoặc tổ chức).
4. Thanh toán phí hàng năm.
Sau khi Apple xử lý đơn đăng ký, bạn sẽ được cấp quyền truy cập vào [App Store Connect](https://appstoreconnect.apple.com). Thông thường quá trình đăng ký mất đến 48 giờ. Với tổ chức, có thể lâu hơn nếu cần xác minh D-U-N-S.
## Bước 2. Thiết lập ứng dụng trong App Store Connect \{#step-2-set-up-your-app-in-app-store-connect\}
Trước khi có thể bán in-app purchase, bạn cần hoàn tất các bước thiết lập ban đầu trong App Store Connect, bao gồm ký thỏa thuận, thêm thông tin thanh toán và đăng ký ứng dụng.
### Ký Paid Applications Agreement \{#sign-the-paid-applications-agreement\}
Apple yêu cầu bạn ký Paid Applications Agreement trước khi bán trên App Store. Điều này áp dụng cho cả ứng dụng trả phí lẫn in-app purchase trong ứng dụng miễn phí.
1. Vào trang **Business** trong [App Store Connect](https://appstoreconnect.apple.com/business).
2. Tìm thỏa thuận **Paid Apps** và nhấn **Review and Agree**.
3. Điền đầy đủ các thông tin yêu cầu:
- **Banking information**: Thêm tài khoản ngân hàng để Apple chuyển tiền thanh toán cho bạn.
- **Tax information**: Điền các mẫu thuế cho các quốc gia bạn muốn bán.
- **Contact information**: Cung cấp thông tin liên hệ của bạn.
:::important
Bạn phải hoàn tất cả ba phần (ngân hàng, thuế, liên hệ) để thỏa thuận có hiệu lực. Cho đến khi thỏa thuận có hiệu lực, bạn không thể bán in-app purchase.
:::
### Tạo Bundle ID \{#create-a-bundle-id\}
Bundle ID giúp xác định duy nhất ứng dụng của bạn trong hệ sinh thái Apple. Bạn cần nó để đăng ký ứng dụng trong App Store Connect và cấu hình tích hợp Adapty.
1. Mở [Apple Developer portal](https://developer.apple.com/account).
2. Vào **Certificates, Identifiers & Profiles** → **Identifiers**.
3. Nhấn **+** để đăng ký định danh mới.
4. Chọn **App IDs** và nhấn **Continue**.
5. Chọn **App** làm loại và nhấn **Continue**.
6. Điền các trường thông tin:
- **Description**: Tên giúp bạn nhận biết Bundle ID này (ví dụ: "My Subscription App").
- **Bundle ID**: Chọn **Explicit** và nhập mã định danh duy nhất theo định dạng reverse-domain (ví dụ: `com.yourcompany.yourapp`).
7. Trong phần **Capabilities**, cuộn xuống và chọn **In-App Purchase**.
8. Nhấn **Continue**, rồi **Register**.
### Đăng ký ứng dụng trong App Store Connect \{#register-your-app-in-app-store-connect\}
1. Vào trang **Apps** trong [App Store Connect](https://appstoreconnect.apple.com/apps).
2. Nhấn **+** → **New App**.
3. Điền các trường bắt buộc:
- **Platforms**: Chọn **iOS**.
- **Name**: Tên ứng dụng sẽ hiển thị trên App Store.
- **Primary language**: Ngôn ngữ mặc định cho metadata của ứng dụng.
- **Bundle ID**: Chọn Bundle ID bạn đã tạo ở bước trước.
- **SKU**: Mã định danh duy nhất cho ứng dụng (người dùng không nhìn thấy). Ví dụ: `my_subscription_app_2025`.
4. Nhấn **Create**.
Ứng dụng của bạn đã được đăng ký trong App Store Connect và sẵn sàng để tích hợp Adapty.
## Tiếp theo \{#whats-next\}
- [Tích hợp ban đầu với App Store](initial_ios): Kết nối ứng dụng App Store của bạn với Adapty
- [Tích hợp SDK](quickstart-sdk): Tích hợp Adapty SDK vào code ứng dụng
- [Kiểm thử trong Sandbox](test-purchases-in-sandbox): Kiểm tra in-app purchase trước khi phát hành
- [Nộp ứng dụng iOS lên App Store](submit-app-to-app-store): Tải bản build lên và nộp để Apple xét duyệt
- [App Store Small Business Program](app-store-small-business-program): Giảm hoa hồng App Store từ 30% xuống 15%
---
# File: app-store-products
---
---
title: "Sản phẩm trên App Store"
description: "Quản lý sản phẩm App Store hiệu quả bằng các công cụ gói đăng ký của Adapty."
---
Trang này hướng dẫn cách tạo sản phẩm trong App Store Connect. Mặc dù thông tin này không trực tiếp liên quan đến chức năng của Adapty, nhưng đây là tài liệu hữu ích nếu bạn gặp khó khăn khi tạo sản phẩm trong tài khoản App Store Connect của mình.
Để tạo sản phẩm và liên kết với Adapty:
1. Mở **App Store Connect**. Vào mục [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) trong menu bên trái.
2. Nếu bạn chưa tạo nhóm gói đăng ký, hãy nhấn nút **Create** dưới tiêu đề **Subscription Groups** để bắt đầu. [Subscription Groups](https://developer.apple.com/help/app-store-connect/manage-subscriptions/offer-auto-renewable-subscriptions) trong App Store Connect giúp phân loại và quản lý các sản phẩm của bạn, cho phép người dùng chuyển đổi giữa các gói một cách liền mạch. Lưu ý rằng không thể tạo gói đăng ký nằm ngoài một nhóm.
3. Trong cửa sổ **Create Subscription Group** vừa mở, nhập tên nhóm gói đăng ký mới vào trường **Reference Name**. Reference Name là nhãn hoặc định danh do bạn đặt, giúp bạn phân biệt và quản lý các nhóm gói đăng ký khác nhau trong ứng dụng.
Reference Name không hiển thị với người dùng; đây chủ yếu là để bạn sử dụng nội bộ và sắp xếp tổ chức. Nó giúp bạn dễ dàng nhận biết và tham chiếu đến các nhóm gói đăng ký cụ thể khi quản lý trong giao diện App Store Connect. Điều này đặc biệt hữu ích nếu bạn có nhiều gói đăng ký hoặc muốn phân loại chúng theo cách phù hợp với cấu trúc ứng dụng của mình.
4. Nhấn nút **Create** để xác nhận tạo nhóm gói đăng ký.
5. Nhóm gói đăng ký được tạo và mở ra. Bây giờ bạn có thể tạo các gói đăng ký trong nhóm. Nhấn nút **Create** dưới tiêu đề **Subscriptions**. Nếu bạn thêm gói đăng ký mới vào nhóm đã có, hãy nhấn nút **Plus** bên cạnh tiêu đề **Subscriptions**.
6. Trong cửa sổ **Create Subscription** vừa mở, nhập tên vào trường **Reference Name** và mã định danh duy nhất của gói đăng ký vào trường **Product ID**.
Reference Name là định danh độc nhất trong App Store Connect cho in-app purchase của bạn. Người dùng sẽ không thấy tên này trên App Store. Chúng tôi khuyên bạn nên sử dụng mô tả rõ ràng, dễ đọc, phản ánh chính xác gói đăng ký bạn định tạo. Lưu ý rằng tên này không được vượt quá 64 ký tự.
Product ID là định danh dạng chữ-số duy nhất, cần thiết để truy cập sản phẩm trong quá trình phát triển và đồng bộ hóa với Adapty — dịch vụ quản lý in-app purchase. Product ID chỉ được chứa ký tự chữ-số, dấu chấm và dấu gạch dưới.
7. Nhấn nút **Create** để xác nhận tạo gói đăng ký.
8. Gói đăng ký được tạo và mở ra. Bây giờ hãy chọn thời hạn của gói đăng ký trong danh sách **Subscription Duration**. Dù thời hạn đã được đề cập trong tên gói đăng ký, bạn vẫn cần điền vào trường **Subscription Duration**.
9. Tiếp theo, hãy thiết lập giá cho gói đăng ký. Nhấn nút **Add Subscription Price** dưới tiêu đề Subscription Prices. Bạn có thể cần cuộn xuống để tìm thấy nút này.
10. Trong cửa sổ **Subscription Price** vừa mở, chọn quốc gia cơ sở trong danh sách **Country or Region** và đơn vị tiền tệ cơ sở trong danh sách **Price**. Sau đó, Apple sẽ tự động tính giá cho tất cả 175 quốc gia hoặc khu vực dựa trên mức giá cơ sở này và tỷ giá hối đoái mới nhất.
11. Nhấn nút **Next**. Trong cửa sổ **Price by Country or Region** vừa mở, bạn sẽ thấy giá đã được tính lại tự động cho tất cả các quốc gia. Bạn có thể thay đổi nếu muốn.
12. Sau khi cập nhật giá theo khu vực, nhấn nút **Next** ở cuối cửa sổ để tiếp tục.
13. Trong cửa sổ **Confirm Subscription Price?** vừa mở, hãy xem xét kỹ các mức giá cuối cùng. Để chỉnh sửa giá, bạn có thể nhấn nút **Back** để quay lại cửa sổ **Price by Country or Region** và cập nhật lại. Khi đã hài lòng với các mức giá, nhấn nút **Confirm**.
14. Sau khi đóng cửa sổ **Confirm Subscription Price?**, nhớ nhấn nút **Save** trong cửa sổ gói đăng ký của bạn. Nếu không, gói đăng ký sẽ không được tạo và toàn bộ dữ liệu đã nhập sẽ bị mất.
Lưu ý rằng các bước trên tập trung vào việc cấu hình Auto-Renewable Subscription. Tuy nhiên, nếu bạn muốn thiết lập các loại in-app purchase khác, hãy nhấn vào tab **In-App Purchases** trên thanh sidebar thay vì "Subscriptions". Đây là nơi bạn có thể quản lý và tạo nhiều loại in-app purchase khác nhau.
### Thêm sản phẩm vào Adapty \{#add-products-to-adapty\}
Sau khi hoàn tất việc thêm in-app purchase, gói đăng ký và ưu đãi trong App Store Connect, bước tiếp theo là [thêm các sản phẩm này vào Adapty](create-product).
---
# File: apple-app-privacy
---
---
title: "Quyền riêng tư ứng dụng Apple"
description: "Tìm hiểu về chính sách quyền riêng tư ứng dụng của Apple và tác động của chúng đến ứng dụng đăng ký của bạn."
---
Apple yêu cầu khai báo quyền riêng tư cho tất cả ứng dụng mới và các bản cập nhật ứng dụng, cả trong phần **App Privacy** của App Store Connect lẫn trong file manifest của ứng dụng. Adapty là một dependency bên thứ ba trong ứng dụng của bạn, vì vậy bạn cần khai báo cách sử dụng Adapty liên quan đến dữ liệu người dùng.
## File manifest quyền riêng tư ứng dụng Apple \{#apple-app-privacy-manifest\}
[File privacy manifest](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests), có tên `PrivacyInfo.xcprivacy`, mô tả dữ liệu riêng tư mà ứng dụng của bạn sử dụng và lý do. Mỗi chủ sở hữu ứng dụng đều phải tạo file manifest cho ứng dụng của mình. Ngoài ra, nếu bạn tích hợp thêm các SDK khác, hãy đảm bảo rằng các file manifest của những SDK nằm trong danh sách [SDKs that require a privacy manifest and signature](https://developer.apple.com/support/third-party-SDK-requirements/) đã được đưa vào. Khi bạn build ứng dụng, Xcode sẽ lấy tất cả các file manifest này và gộp chúng lại thành một.
Mặc dù Adapty không có trong danh sách [SDKs that require a privacy manifest and signature](https://developer.apple.com/support/third-party-SDK-requirements/), nhưng Adapty SDK từ phiên bản 2.10.2 trở lên đã bao gồm file này để tiện cho bạn. Hãy đảm bảo cập nhật SDK để có được file manifest.
Mặc dù Adapty không yêu cầu bất kỳ dữ liệu nào phải được đưa vào file manifest (còn gọi là báo cáo quyền riêng tư ứng dụng), nhưng nếu bạn đang dùng `customerUserId` của Adapty để theo dõi, bạn cần khai báo điều đó trong file manifest như sau:
1. Thêm một dictionary vào mảng `NSPrivacyCollectedDataTypes` trong file thông tin quyền riêng tư của bạn.
2. Thêm các key `NSPrivacyCollectedDataType`, `NSPrivacyCollectedDataTypeLinked`, và `NSPrivacyCollectedDataTypeTracking` vào dictionary.
3. Thêm chuỗi `NSPrivacyCollectedDataTypeUserID` (định danh của loại dữ liệu `UserID` trong [Danh sách các danh mục và loại dữ liệu cần khai báo trong file manifest](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests#Describe-the-data-your-app-or-third-party-SDK-collects)) cho key `NSPrivacyCollectedDataType` trong dictionary `NSPrivacyCollectedDataTypes` của bạn.
4. Thêm `true` cho các key `NSPrivacyCollectedDataTypeTracking` và `NSPrivacyCollectedDataTypeLinked` trong dictionary `NSPrivacyCollectedDataTypes` của bạn.
5. Dùng chuỗi `NSPrivacyCollectedDataTypePurposeProductPersonalization` làm giá trị cho key `NSPrivacyCollectedDataTypePurposes` trong dictionary `NSPrivacyCollectedDataTypes` của bạn.
Nếu bạn nhắm mục tiêu paywall đến các đối tượng với thuộc tính tùy chỉnh, hãy cân nhắc kỹ những thuộc tính tùy chỉnh bạn sử dụng và xem chúng có khớp với [các danh mục và loại dữ liệu cần khai báo trong file manifest](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests) hay không. Nếu có, hãy lặp lại các bước trên cho từng loại dữ liệu.
Sau khi khai báo tất cả các loại và danh mục dữ liệu bạn thu thập, hãy tạo báo cáo quyền riêng tư cho ứng dụng của bạn như mô tả trong [tài liệu Apple](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests#Create-your-apps-privacy-report).
## Khai báo quyền riêng tư ứng dụng Apple trong App Store Connect \{#apple-app-privacy-disclosure-in-app-store-connect\}
1. Trong [App Store Connect](https://appstoreconnect.apple.com/), mở ứng dụng của bạn và vào **App Privacy**. Nhấp **Get Started**.
2. Chọn **Yes, we collect data from this app** và nhấp **Next**.
### Các loại dữ liệu \{#data-types\}
Bảng dưới đây liệt kê các loại dữ liệu mà Apple yêu cầu bạn khai báo và cho biết loại nào Adapty cần. **Phần này chỉ đề cập đến Adapty.** Nếu ứng dụng của bạn thu thập thêm dữ liệu qua các SDK khác hoặc code của bạn, hãy chọn thêm những loại dữ liệu đó.
✅ = Adapty yêu cầu
👀 = Có thể cần thiết \(xem chi tiết bên dưới\)
❌ = Adapty không yêu cầu — chọn nếu ứng dụng của bạn thu thập dữ liệu này qua các phương tiện khác
| Loại dữ liệu | Yêu cầu | Ghi chú |
|--------------------------------------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| Identifiers | ✅ | Nếu bạn nhận dạng người dùng bằng customerUserId, hãy chọn 'User ID'.
Adapty thu thập IDFA, vì vậy bạn phải chọn 'Device ID'.
| | Purchases | ✅ | Adapty thu thập lịch sử mua hàng của người dùng. | | Contact Info, bao gồm tên, số điện thoại hoặc địa chỉ email | 👀 | Bắt buộc nếu bạn truyền dữ liệu cá nhân như tên, số điện thoại hoặc địa chỉ email bằng phương thức **`updateProfile`**. | | Usage Data | 👀 | Nếu bạn đang dùng các SDK analytics như Amplitude, Mixpanel, AppMetrica hoặc Firebase, điều này có thể được yêu cầu. | | Location | ❌ | Adapty không thu thập dữ liệu vị trí chính xác. Chọn nếu ứng dụng của bạn thu thập. | | Health & Fitness | ❌ | Adapty không thu thập dữ liệu sức khỏe hoặc thể dục. Chọn nếu ứng dụng của bạn thu thập. | | Sensitive Info | ❌ | Adapty không thu thập thông tin nhạy cảm. Chọn nếu ứng dụng của bạn thu thập. | | User Content | ❌ | Adapty không thu thập nội dung người dùng. Chọn nếu ứng dụng của bạn thu thập. | | Diagnostics | ❌ | Adapty không thu thập dữ liệu chẩn đoán. Chọn nếu ứng dụng của bạn thu thập. | | Browsing History | ❌ | Adapty không thu thập lịch sử duyệt web. Chọn nếu ứng dụng của bạn thu thập. | | Search History | ❌ | Adapty không thu thập lịch sử tìm kiếm. Chọn nếu ứng dụng của bạn thu thập. | | Contacts | ❌ | Adapty không thu thập danh sách liên hệ. Chọn nếu ứng dụng của bạn thu thập. | | Financial Info | ❌ | Adapty không thu thập thông tin tài chính. Chọn nếu ứng dụng của bạn thu thập. | ### Các loại dữ liệu bắt buộc \{#required-data-types\} #### Purchases \{#purchases\} Khi sử dụng Adapty, bạn phải khai báo rằng ứng dụng của bạn thu thập **Purchase History**.
#### Identifiers \{#identifiers\}
Khi sử dụng Adapty, bạn phải khai báo các identifier sau:
- **Device ID** — Adapty thu thập IDFA.
- **User ID** — bắt buộc nếu bạn nhận dạng người dùng bằng **`customerUserId`**.
### Mục đích sử dụng dữ liệu \{#data-usage\}
Sau khi lưu **Data types**, bạn sẽ cần chỉ rõ dữ liệu được sử dụng như thế nào:
1. Nhấp **Set up purchase history** trong khối **Purchases**.
2. Khi Apple hỏi dữ liệu lịch sử mua hàng được sử dụng như thế nào, hãy chọn các mục sau cho Adapty:
- **Analytics** — Adapty sử dụng lịch sử mua hàng cho analytics doanh thu, cohort và các chỉ số.
- **Product Personalization** — Adapty sử dụng dữ liệu mua hàng để phân khúc đối tượng và nhắm mục tiêu paywall.
- **App Functionality** — Adapty xác thực các giao dịch mua, quản lý mức độ truy cập và theo dõi trạng thái gói đăng ký.
Chọn thêm các mục đích khác nếu ứng dụng của bạn sử dụng dữ liệu mua hàng theo những cách khác (ví dụ: nếu bạn gửi sự kiện mua hàng đến các nền tảng quảng cáo qua tích hợp Adapty).
3. Nhấp **Next**.
4. Đối với cả **Device ID** và **User ID** (nếu được sử dụng):
1. Nhấp **Set up user/device ID** trong khối **User/Device ID**.
2. Khi Apple hỏi dữ liệu identifier được sử dụng như thế nào, hãy chọn các mục sau cho Adapty:
- **App Functionality** — Adapty sử dụng identifier để quản lý hồ sơ người dùng, liên kết các giao dịch mua và theo dõi mức độ truy cập.
Nếu bạn gửi dữ liệu attribution đến các nền tảng bên thứ ba qua tích hợp Adapty (như AppsFlyer hoặc Adjust), hãy chọn thêm **Third-Party Advertising**. Chọn thêm các mục đích khác nếu ứng dụng của bạn sử dụng identifier theo những cách khác.
5. Nhấp **Next**.
---
# File: apple-family-sharing
---
---
title: "Apple family sharing"
description: "Bật Apple Family Sharing trong Adapty để hỗ trợ gói đăng ký được chia sẻ."
---
Tính năng chia sẻ gia đình của Apple cho phép phân phối in-app purchase cho các thành viên trong gia đình, mang đến cho người dùng của các ứng dụng hướng đến nhóm — như dịch vụ phát video trực tuyến và ứng dụng trẻ em — một cách tiện lợi để chia sẻ gói đăng ký mà không cần dùng chung Apple ID. Bằng cách cho phép tối đa năm thành viên gia đình sử dụng một gói đăng ký, [Family Sharing](https://developer.apple.com/documentation/storekit/supporting-family-sharing-in-your-app) có thể giúp tăng mức độ gắn kết và giữ chân người dùng cho ứng dụng của bạn.
Trong hướng dẫn này, chúng tôi sẽ hướng dẫn cách đăng ký gói đăng ký vào Family Sharing và giải thích cách Adapty quản lý các giao dịch mua được chia sẻ trong gia đình.
Để bắt đầu bật Family Sharing cho một sản phẩm cụ thể, hãy truy cập [App Store Connect](https://appstoreconnect.apple.com/). Family Sharing mặc định bị tắt cho cả in-app purchase mới lẫn hiện có, vì vậy bạn cần bật riêng cho từng in-app purchase. Bạn có thể làm điều này dễ dàng bằng cách vào **trang ứng dụng**, điều hướng đến trang in-app purchase tương ứng và chọn tùy chọn **Turn On** trong phần Family Sharing.
Hãy lưu ý rằng một khi bạn đã bật Family Sharing cho một sản phẩm, **không thể tắt lại nữa**, vì điều này sẽ ảnh hưởng đến trải nghiệm của những người dùng đã chia sẻ gói đăng ký với các thành viên trong gia đình.
Ngoài ra, xin lưu ý rằng chỉ có non-consumable và gói đăng ký mới có thể được chia sẻ.
Trên hộp thoại hiển thị, chỉ cần nhấp vào nút **Confirm** để hoàn tất quá trình thiết lập. Sau đó, phần Family Sharing sẽ cập nhật hiển thị thông báo "This subscription can be shared by everyone in a family group." Điều này xác nhận rằng gói đăng ký hiện đã được bật cho Family Sharing và có thể được chia sẻ với tối đa năm thành viên gia đình.
Adapty giúp bạn hỗ trợ Family Sharing dễ dàng mà không cần thêm bất kỳ thao tác nào. Bạn chỉ cần [cấu hình sản phẩm](app-store-products) từ App Store, và khi **bật** **Family Sharing** từ App Store Connect, tính năng này sẽ tự động khả dụng trong **Adapty** và được nhận dưới dạng sự kiện trên webhook.
:::note
Xin lưu ý rằng Family Sharing không được hỗ trợ trong môi trường sandbox.
:::
Một điều cần lưu ý là khi người dùng mua gói đăng ký và chia sẻ với các thành viên gia đình, sẽ có **độ trễ lên đến một giờ** trước khi gói đăng ký đó khả dụng với họ. Apple thiết kế độ trễ này để người dùng có thời gian thay đổi ý định và hủy chia sẻ nếu muốn. Tuy nhiên, nếu gói đăng ký được gia hạn, sẽ không có độ trễ khi cung cấp cho các thành viên gia đình.
Khi người dùng mua một sản phẩm in-app hỗ trợ Family Sharing, giao dịch sẽ xuất hiện trong biên lai của họ như thường, nhưng có thêm một trường mới là `in_app_ownership_type` với giá trị `PURCHASED.` Ngoài ra, một giao dịch mới sẽ được tạo cho tất cả thành viên gia đình, với `web_order_line_item_id` và `original_transaction_id` khác với giao dịch gốc, cùng trường `in_app_ownership_type` có giá trị `FAMILY_SHARED.`
Để đảm bảo tính chính xác trong tính toán doanh thu, chỉ các giao dịch có `in_app_ownership_type` là `PURCHASED` mới được tính trong Adapty analytics. Các giao dịch `FAMILY_SHARED` bị loại trừ khỏi chỉ số doanh thu và chuyển đổi.
**Các sự kiện được gửi cho giao dịch Family Sharing.**
Giao dịch `FAMILY_SHARED` chỉ kích hoạt sự kiện **Access level updated**. Các sự kiện gói đăng ký theo từng sản phẩm không kích hoạt cho các thành viên gia đình.
| Sự kiện | `FAMILY_SHARED` | `PURCHASED` |
| --- | --- | --- |
| **Access level updated** | Có | Có |
| **Subscription started** | Không | Có |
| **Trial started** | Không | Có |
| **Subscription renewed** | Không | Có |
| **Subscription expired** | Không | Có |
| **Subscription refunded** | Không | Có |
| **Billing issue detected** | Không | Có |
Nếu hệ thống analytics của bạn dựa trên sự kiện **Subscription started**, các thành viên gia đình sẽ không xuất hiện ở đó. Hãy dùng **Access level updated** để phát hiện các thành viên gia đình đang hoạt động.
Để xác định các thành viên gia đình khác trong Adapty, bạn có thể tìm thấy họ trong chi tiết sự kiện. Trước tiên, hãy tìm giao dịch mua gia đình gốc. Sau đó, kiểm tra chi tiết sự kiện của giao dịch đó, đặc biệt chú ý đến cùng sản phẩm, ngày mua và ngày hết hạn. Bằng cách phân tích chi tiết sự kiện, bạn có thể xác định các giao dịch thành viên gia đình khác liên quan đến giao dịch mua gốc.
---
# File: app-store-small-business-program
---
---
title: "App Store Small Business Program"
description: "Tìm hiểu về Chương trình Doanh nghiệp Nhỏ của Apple, tác động của nó đến doanh thu và phân tích của Adapty"
---
:::link
Xem chương trình tương ứng trên Play Store tại [Google Reduced Service Fee](google-reduced-service-fee).
:::
Các tổ chức có doanh thu từ App Store tối đa 1 triệu USD mỗi năm đủ điều kiện tham gia [Chương trình Doanh nghiệp Nhỏ](https://developer.apple.com/app-store/small-business-program/) của Apple. Nếu đăng ký, mức hoa hồng chuẩn 30% của cửa hàng sẽ được giảm xuống còn **15%**.
Thành viên của chương trình cần **thay đổi cài đặt trong Adapty** để đảm bảo tính toán doanh thu chính xác và xử lý sự kiện tích hợp đúng cách.
Bài viết này mô tả:
* [Cách thiết lập Adapty](#configure-adapty) nếu ứng dụng của bạn đã đăng ký Chương trình Doanh nghiệp Nhỏ
* [Cách đăng ký chương trình](#apply-for-the-program) nếu bạn muốn giảm hoa hồng cửa hàng
## Cấu hình Adapty \{#configure-adapty\}
Adapty có thể áp dụng mức hoa hồng giảm vào [analytics](analytics) và [integration events](analytics-integration) của bạn. Để bật tính năng này, hãy chỉ định trạng thái Chương trình Doanh nghiệp Nhỏ cho từng ứng dụng.
:::warning
Hãy cấu hình trạng thái SBP trong Adapty **ngay khi nhận được phê duyệt**. Các thay đổi muộn không thể ghi đè lại các webhook event đã được gửi ([chi tiết](#retroactive-setting-changes)).
:::
1. Mở [**App Settings** → **General**](https://app.adapty.io/account)
2. Tìm mục **Small Business Program**.
3. Nhấn **Add period**.
4. Chọn ngày bắt đầu tham gia.
5. Chọn ngày kết thúc, hoặc bật tùy chọn **At the current moment** để gia hạn trạng thái này vô thời hạn. Nếu bạn [mất điều kiện tham gia](#losing-eligibility) sau này, bạn có thể sửa ngày kết thúc.
6. Nhấn **Apply**.
Nếu tổ chức của bạn vẫn đủ điều kiện tham gia chương trình, tư cách thành viên sẽ được chuyển tiếp sang năm dương lịch tiếp theo. Tuy nhiên, trạng thái thành viên chỉ áp dụng **cho khoảng thời gian bạn chỉ định**.
* Nhấn **Add period** để thêm giai đoạn thành viên mới.
* Để gia hạn trạng thái này vô thời hạn, hãy bật tùy chọn **At the current moment**.
Để xác minh cấu hình, mở [biểu đồ Revenue](revenue) và chọn **Proceeds after store commission**. Xác nhận rằng doanh thu hiển thị phản ánh mức hoa hồng đã giảm.
## Đăng ký chương trình \{#apply-for-the-program\}
### Yêu cầu điều kiện \{#eligibility-requirements\}
Apple xác định điều kiện tham gia SBP dựa trên **doanh thu hàng năm** của bạn — doanh số bán hàng của năm dương lịch trước **sau** khi trừ hoa hồng cửa hàng và thuế.
Để đủ điều kiện, tổng doanh thu hàng năm của tổ chức bạn và các
2. Nhấn nút **Create subscription**.
3. Trong cửa sổ **Create subscription** vừa mở, nhập ID gói đăng ký vào trường **Product ID** và tên gói đăng ký vào trường **Name**.
Product ID phải là duy nhất, phải bắt đầu bằng số hoặc chữ thường, và có thể chứa dấu gạch dưới (\_) và dấu chấm (.). ID này được dùng để truy cập sản phẩm trong quá trình phát triển và đồng bộ hóa với Adapty. Sau khi một Product ID được gán cho sản phẩm trong Google Play Console, nó không thể dùng lại cho bất kỳ ứng dụng nào khác, kể cả khi sản phẩm bị xóa.
Khi đặt tên cho Product ID, nên theo một định dạng chuẩn hóa. Chúng tôi khuyến nghị dùng cách đặt tên ngắn gọn hơn theo dạng `
3. Sau khi chi tiết gói đăng ký mở ra, nhấn nút **Add base plan** bên dưới tiêu đề **Base plans and offers**. Bạn có thể cần cuộn xuống để tìm nút này.
4. Trong cửa sổ **Add base plan** vừa mở, nhập mã định danh duy nhất cho base plan vào trường **Plan ID**. ID phải bắt đầu bằng số hoặc chữ thường, và có thể chứa số (0-9), chữ thường (a-z) và dấu gạch ngang (-), rồi hoàn thiện các trường bắt buộc.
5. Chỉ định giá theo từng khu vực.
6. Nhấn nút **Save** để hoàn tất thiết lập.
7. Nhấn nút **Activate** để kích hoạt base plan.
Lưu ý rằng trong Adapty, sản phẩm gói đăng ký chỉ có thể có một base plan duy nhất với thời hạn và loại gia hạn nhất quán.
### Sản phẩm dự phòng \{#fallback-products\}
:::warning
Hỗ trợ base plan không tương thích ngược
Các phiên bản SDK Adapty cũ hơn không hỗ trợ các tính năng của Google Billing Library v5+, cụ thể là nhiều base plan trên mỗi sản phẩm gói đăng ký và các ưu đãi. Chỉ những base plan được đánh dấu là **[backwards compatible](https://support.google.com/googleplay/android-developer/answer/12124625?hl=en#backwards_compatible)** trong Google Play Console mới có thể truy cập với các phiên bản SDK này. Lưu ý rằng chỉ một base plan mỗi gói đăng ký có thể được đánh dấu là tương thích ngược.
:::
Để tận dụng tối đa các cấu hình và tính năng gói đăng ký Google nâng cao trong Adapty, chúng tôi cung cấp khả năng thiết lập sản phẩm dự phòng tương thích ngược. Sản phẩm dự phòng này chỉ được sử dụng cho các ứng dụng dùng phiên bản SDK Adapty cũ hơn. Khi tạo sản phẩm Google Play, bạn có thể chỉ định liệu sản phẩm có nên được đánh dấu là tương thích ngược trong Play Console hay không. Adapty sử dụng thông tin này để xác định liệu sản phẩm có thể được mua bởi các phiên bản SDK cũ hơn (phiên bản 2.5 trở xuống) hay không.
Giả sử bạn có gói đăng ký tên `subscription.premium` cung cấp hai base plan: hàng tuần (tương thích ngược) và hàng tháng. Nếu bạn thêm sản phẩm `subscription.premium:weekly` vào Adapty, bạn không cần chỉ định sản phẩm tương thích ngược. Tuy nhiên, với sản phẩm `subscription.premium:monthly`, bạn sẽ cần chỉ định một sản phẩm tương thích ngược. Nếu không làm vậy, người dùng có thể vô tình mua sản phẩm `subscription.premium:weekly` trong Google Billing Library thứ 4. Để giải quyết trường hợp này, bạn nên tạo một sản phẩm riêng có base plan cũng là hàng tháng và được đánh dấu là tương thích ngược. Điều này đảm bảo rằng người dùng chọn tùy chọn `subscription.premium:monthly` sẽ được tính phí đúng theo chu kỳ dự kiến.
## Thêm sản phẩm vào Adapty \{#add-products-to-adapty\}
Sau khi hoàn tất việc thêm in-app purchase, gói đăng ký và ưu đãi trong App Store Connect, bước tiếp theo là [thêm các sản phẩm này vào Adapty](create-product).
---
# File: google-play-data-safety
---
---
title: "Google Play Data Safety"
description: "Đảm bảo tuân thủ chính sách Google Play Data Safety trong Adapty."
---
Phần Data Safety trên Google Play cung cấp cho nhà phát triển ứng dụng một cách đơn giản để thông báo cho người dùng về dữ liệu mà ứng dụng thu thập hoặc chia sẻ, đồng thời làm nổi bật các biện pháp bảo mật và quyền riêng tư quan trọng. Thông tin này giúp người dùng đưa ra quyết định sáng suốt hơn khi chọn ứng dụng để tải về và sử dụng.
Dưới đây là hướng dẫn ngắn gọn về dữ liệu mà Adapty thu thập, giúp bạn cung cấp thông tin cần thiết cho Google Play.
## Thu thập dữ liệu và bảo mật \{#data-collection-and-security\}
**Ứng dụng của bạn có thu thập hoặc chia sẻ bất kỳ loại dữ liệu người dùng nào theo yêu cầu không?**
Chọn 'Yes' vì Adapty thu thập lịch sử mua hàng của khách hàng.
**Toàn bộ dữ liệu người dùng mà ứng dụng thu thập có được mã hóa trong quá trình truyền không?**
Chọn 'Yes' vì Adapty mã hóa dữ liệu trong quá trình truyền.
**Bạn có cung cấp cách để người dùng yêu cầu xóa dữ liệu của họ không?**
Nếu chọn 'Yes', hãy đảm bảo khách hàng của bạn có cách liên hệ với đội ngũ hỗ trợ để yêu cầu xóa dữ liệu. Bạn có thể xóa khách hàng trực tiếp từ Adapty dashboard hoặc thông qua REST API.
## Loại dữ liệu \{#data-types\}
Dưới đây là danh sách các loại dữ liệu mà Google yêu cầu báo cáo, cùng với thông tin về việc Adapty có thu thập từng loại dữ liệu cụ thể hay không.
| Loại dữ liệu | Chi tiết |
| :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Location | Adapty không thu thập |
| Health and Fitness | Adapty không thu thập |
| Photos and Videos | Adapty không thu thập |
| Files and Docs | Adapty không thu thập |
| Calendar | Adapty không thu thập |
| Contacts | Adapty không thu thập |
| User Content | Adapty không thu thập |
| Browsing History | Adapty không thu thập |
| Search History | Adapty không thu thập |
| App Info and Performance | Adapty không thu thập |
| Web Browsing | Adapty không thu thập |
| Contact Info | Adapty không thu thập |
| Financial Info | Adapty thu thập lịch sử mua hàng của người dùng |
| Personal Info and Identifiers | Adapty thu thập User ID và một số thông tin nhận dạng khác bao gồm tên, địa chỉ email, số điện thoại, v.v., nếu bạn chủ động truyền chúng vào Adapty SDK. |
| Device and other identifiers | Adapty thu thập dữ liệu về ID thiết bị. |
## Mục đích và cách xử lý dữ liệu \{#data-usage-and-handling\}
### User ID \{#user-ids\}
**1. Dữ liệu này được thu thập, chia sẻ hay cả hai?**
Dữ liệu này được Adapty thu thập. Nếu bạn đang sử dụng các tích hợp giữa Adapty và bên thứ ba không được coi là nhà cung cấp dịch vụ, bạn có thể cần khai báo thêm "Shared" ở đây.
**2. Dữ liệu này có được xử lý tạm thời không?**
Chọn 'No'.
**3. Dữ liệu này có bắt buộc đối với ứng dụng của bạn không, hay người dùng có thể lựa chọn không cho thu thập?**
Việc thu thập dữ liệu này là bắt buộc và không thể tắt.
**4. Tại sao dữ liệu người dùng này được thu thập? / Tại sao dữ liệu người dùng này được chia sẻ?**
Chọn các ô 'App functionality' và 'Analytics'.
### Financial Info \{#financial-info\}
Nếu bạn đang sử dụng Adapty, bạn phải khai báo rằng ứng dụng của mình thu thập thông tin 'Purchase history' trong phần Data types trên Google Play Console.
### Device or other IDs \{#device-or-other-ids\}
## Các bước tiếp theo \{#next-steps\}
Sau khi hoàn thành các lựa chọn về data safety, Google sẽ hiển thị bản xem trước phần quyền riêng tư của ứng dụng bạn. Nếu bạn đã chọn "Financial Info" và "Device or other IDs" như đề cập ở trên, thông tin quyền riêng tư sẽ hiển thị tương tự như ví dụ sau:
Nếu bạn đã sẵn sàng gửi ứng dụng để App Review, hãy tham khảo tài liệu [Release Checklist](release-checklist) của chúng tôi để biết thêm hướng dẫn chuẩn bị gửi ứng dụng.
---
# File: google-reduced-service-fee
---
---
title: "Phí Dịch Vụ Giảm của Google"
description: "Tìm hiểu về Phí Dịch Vụ Giảm của Google, tác động của nó đến doanh thu và phân tích của Adapty"
---
:::link
Để xem chương trình tương ứng trên App Store, hãy xem [Chương trình Doanh nghiệp Nhỏ của App Store](app-store-small-business-program).
:::
[Chương trình Phí Dịch Vụ Giảm](https://support.google.com/googleplay/android-developer/answer/112622?hl=en) của Google Play giảm hoa hồng trên 1 triệu USD đầu tiên trong thu nhập hàng năm từ 30% xuống còn **15%**. Thu nhập vượt quá 1 triệu USD trong cùng năm dương lịch sẽ bị tính theo mức chuẩn 30%.
:::note
Kể từ ngày 1 tháng 1 năm 2022, Google tính 15% trên tất cả các gói đăng ký tự gia hạn bất kể chương trình này. Phí Dịch Vụ Giảm chủ yếu có lợi cho in-app purchase không phải gói đăng ký và ứng dụng trả phí.
:::
Các thành viên của chương trình phải **thay đổi cài đặt Adapty** để đảm bảo tính toán doanh thu chính xác và xử lý sự kiện tích hợp đúng cách.
Bài viết này mô tả:
* [Cách thiết lập Adapty](#configure-adapty) nếu ứng dụng của bạn đã đăng ký chương trình Phí Dịch Vụ Giảm
* [Cách đăng ký chương trình](#enroll-in-the-program) nếu bạn muốn giảm hoa hồng cửa hàng
## Cấu hình Adapty \{#configure-adapty\}
Adapty có thể áp dụng mức hoa hồng giảm cho [analytics](analytics) và [sự kiện tích hợp](analytics-integration) của bạn. Để bật tính năng này, hãy chỉ định trạng thái Phí Dịch Vụ Giảm của bạn theo từng ứng dụng.
:::warning
Cấu hình trạng thái Phí Dịch Vụ Giảm trong Adapty **ngay khi bạn đăng ký**. Những thay đổi muộn không thể ghi đè lại các sự kiện webhook đã được gửi ([chi tiết](#retroactive-setting-changes)).
:::
1. Mở [**App Settings** → **General**](https://app.adapty.io/account).
2. Tìm mục **Reduced Service Fee**.
3. Nhấp **Add period**.
4. Chọn ngày bắt đầu tham gia.
5. Chọn ngày kết thúc, hoặc bật tùy chọn **At the current moment** để kéo dài trạng thái này vô thời hạn. Nếu [thu nhập hàng năm của bạn vượt quá 1 triệu USD](#exceeding-the-threshold), bạn có thể sửa ngày kết thúc.
6. Nhấp **Apply**.
Trạng thái thành viên chỉ áp dụng **cho khoảng thời gian bạn chỉ định**. Chương trình được đặt lại mỗi năm dương lịch.
* Nhấp **Add period** để thêm khoảng thời gian thành viên mới.
* Để kéo dài trạng thái này vô thời hạn, hãy bật tùy chọn **At the current moment**.
Để xác minh cấu hình, mở [biểu đồ Doanh thu](revenue) và chọn **Proceeds after store commission**. Xác nhận rằng doanh thu hiển thị phản ánh mức hoa hồng giảm.
## Đăng ký chương trình \{#enroll-in-the-program\}
### Điều kiện tham gia \{#eligibility-requirements\}
Google xác định điều kiện tham gia dựa trên **thu nhập hàng năm** của bạn trên tất cả các tài khoản trong | Tùy chọn | Mô tả | | ------- | ------------------------------------------------------------ | | Opt-out | (mặc định) Nếu Adapty không biết trạng thái đồng ý của người dùng, hệ thống giả định rằng sự đồng ý **đã được cấp** và Refund Saver **sẽ chia sẻ** dữ liệu liên quan đến hoàn tiền với Apple. | | Opt-in | Nếu Adapty không biết trạng thái đồng ý của người dùng, hệ thống giả định rằng sự đồng ý **chưa được cấp** và Refund Saver **sẽ không chia sẻ** bất kỳ dữ liệu nào với Apple. Đây là cách tiếp cận được Apple khuyến nghị. | ## Cập nhật sự đồng ý của người dùng trong SDK \{#update-user-consent-in-the-sdk\} Để thông báo cho Adapty biết một người dùng cụ thể có đồng ý hay không, hãy sử dụng phương thức `updateCollectingRefundDataConsent`. Giá trị này được lưu trữ phía server theo từng hồ sơ người dùng, vì vậy bạn chỉ cần gọi khi sự đồng ý thay đổi.
:::note Để theo dõi các sự kiện gói đăng ký, hãy dùng tích hợp [Webhook](webhook) trong Adapty hoặc tích hợp trực tiếp với dịch vụ hiện có của bạn. ::: ## Trường hợp 1: Đồng bộ người dùng đăng ký giữa web và mobile \{#case-1-sync-subscribers-between-web-and-mobile\} Nếu bạn dùng các nhà cung cấp thanh toán web như Stripe, ChargeBee hoặc các dịch vụ khác, bạn có thể dễ dàng đồng bộ người dùng đăng ký. Cách thực hiện: 1.
2. Đặt tên mô tả cho onboarding của bạn và nhấp **Proceed to build onboarding**.
3. Bạn sẽ được chuyển đến trình xây dựng onboarding.
Trang này có sẵn một mẫu demo mặc định để bạn tham khảo, giúp hiểu cách onboarding thu thập dữ liệu và cách cá nhân hóa chúng bằng biến và câu hỏi. Hãy thoải mái xóa các màn hình không cần thiết và [thiết kế trải nghiệm onboarding của riêng bạn](design-onboarding) tại đây.
4. Khi đã sẵn sàng, nhấp nút **Preview** ở góc trên bên phải. Tự trải nghiệm toàn bộ flow onboarding để đảm bảo mọi thứ hoạt động đúng như mong đợi.
5. Nếu mọi thứ hoạt động tốt, nhấp **Publish** ở góc trên bên phải. Vui lòng chờ cho đến khi quá trình xuất bản hoàn tất trước khi quay lại Adapty. Nếu không, tiến trình của bạn sẽ bị mất.
:::danger
Nếu bạn không nhấp **Publish**, SDK sẽ không thể lấy onboarding bạn đã tạo.
:::
Sau khi onboarding được xuất bản, nhấp **Back to Adapty**. Onboarding của bạn đã được tạo và bạn có thể thêm nó vào một placement để bắt đầu sử dụng.
## Bước 2. Tạo placement cho onboarding của bạn \{#step-2-create-a-placement-for-your-onboarding\}
1. Vào **Placements** từ menu chính và chuyển sang tab **Onboardings**. Nhấp **Create placement**.
2. Nhập tên và ID cho placement. Sau đó, nhấp **Run onboarding** và chọn onboarding hiển thị cho tất cả người dùng.
3. Nếu bạn đã chuẩn bị một onboarding riêng cho một nhóm người dùng cụ thể, [thêm nhiều đối tượng hơn](audience) và chọn onboarding khác cho họ.
## Bước 3. Tích hợp onboarding vào ứng dụng của bạn \{#step-3-integrate-the-onboarding-into-your-app\}
:::important
Onboarding khả dụng cho các ứng dụng sử dụng Adapty SDK v3.8.0 trở lên (iOS, Android, React Native, Flutter), v3.14.0 trở lên (Unity), hoặc v3.15.0 trở lên (Kotlin Multiplatform, Capacitor).
:::
Để bắt đầu hiển thị onboarding trong ứng dụng, hãy tích hợp chúng qua Adapty SDK:
- [iOS](ios-onboardings)
- [Android](android-onboardings)
- [React Native](react-native-onboardings)
- [Flutter](flutter-onboardings)
- [Unity](unity-onboardings)
- [Kotlin Multiplatform](kmp-onboardings)
- [Capacitor](capacitor-onboardings)
Để biết onboarding nào hoạt động hiệu quả hơn, bạn cũng có thể chạy [A/B test](ab-tests).
---
# File: design-onboarding
---
---
title: "Thiết kế onboarding"
description: "Tạo onboarding ý nghĩa."
---
Trình tạo onboarding ứng dụng di động không cần code là một công cụ mạnh mẽ và linh hoạt, giúp bạn mang lại trải nghiệm onboarding tốt nhất cho người dùng. Bạn không cần phải là lập trình viên hay designer để có được kết quả tuyệt vời.
## Màn hình onboarding \{#onboarding-screens\}
Flow onboarding bao gồm nhiều màn hình mà bạn thêm vào và thiết kế.
Người dùng sẽ nhấn nút để điều hướng giữa các màn hình.
:::tip
Nếu một số người dùng cần một flow hơi khác (ví dụ: trong ứng dụng fitness, bạn có thể muốn hiển thị hình ảnh "mục tiêu" khác nhau tùy theo giới tính của người dùng), bạn không cần tạo các onboarding riêng biệt.
Thay vào đó, bạn có thể ẩn một số màn hình theo mặc định và chỉ hiển thị chúng trong một số trường hợp nhất định.
:::
## Các thành phần onboarding \{#onboarding-elements\}
Các thành phần onboarding được hiển thị ở bên trái theo thứ tự chúng xuất hiện. Nhấn **Add** ở góc trên bên phải để thêm thành phần mới.
Có các nhóm thành phần sau mà bạn có thể thêm:
- **Containers**: Container cho phép bạn thiết lập bố cục linh hoạt. Ví dụ: nếu bạn muốn thêm văn bản hai cột, bạn cần thêm **Columns** rồi kéo hai khối văn bản vào **Columns** trên bảng bên trái. Hoặc nếu bạn đang thêm carousel, bạn cần thêm hình ảnh vào các thành phần **Media** bên trong.
- **Typography**: Thêm các khối văn bản được định dạng sẵn và tùy chỉnh giao diện theo nhu cầu.
- **Media & Display**: Ngoài hình ảnh và video, bạn có thể thêm các biểu đồ động thể hiện giá trị ứng dụng của mình và khuyến khích người dùng.
**Các định dạng video được hỗ trợ** là MP4 và WebM. **Kích thước file media tối đa** là 15 MB.
Nếu bạn muốn thêm một thành phần hoạt hình không được hỗ trợ (như Lottie), bạn có thể chuyển đổi nó thành video (ví dụ: với [công cụ này](https://www.lottielab.com/lottie/lottie-to-video)) và nhúng vào dưới dạng video.
- **Quiz**: Tạo các bảng câu hỏi ngắn với tùy chọn văn bản và hình ảnh để tùy chỉnh trải nghiệm onboarding và hiểu người dùng của bạn hơn.
- **Inputs**: Thu thập dữ liệu từ người dùng.
- **Buttons**: Nút cho phép người dùng điều hướng giữa các màn hình, đóng onboarding hoặc chuyển đến paywall. Bạn cũng có thể thêm nút bóng bẩy hoặc chuyển động để thu hút sự chú ý của người dùng và chuyển đổi lượt cài đặt thành giao dịch mua.
- **Loaders**: Các loader có hoạt ảnh giữ cho người dùng tập trung trong quá trình chờ.
- **User engagement**: Thêm lời chứng thực, danh sách email người dùng và đếm ngược.
:::note
Trong nhóm **Media & Display**, bạn cũng có thể thêm mã HTML tùy chỉnh nếu các tùy chọn tùy chỉnh hiện có chưa đủ.
Tuy nhiên, các thành phần HTML tùy chỉnh không được tải trước hay lưu vào bộ nhớ cache, vì vậy nên sử dụng **Raw HTML** chỉ cho các thành phần nhỏ, nhẹ.
:::
### ID thành phần và ID hành động \{#element-id-and-action-id\}
Nếu bạn muốn sử dụng một nút cho các hành động tùy chỉnh, hãy gán cho nó một **action ID** rồi sử dụng nó trong mã nguồn của bạn. Action ID cho phép bạn xử lý các nút khác nhau có cùng action ID theo cùng một cách.
Nếu bạn muốn xử lý dữ liệu nhập của người dùng trong một trường cụ thể (ví dụ: lưu tuổi hoặc email của họ), hãy gán cho nó một **element ID** rồi sử dụng nó trong mã nguồn để liên kết câu hỏi với câu trả lời. Element ID chỉ được sử dụng một lần trong onboarding của bạn.
## Tùy chọn tùy chỉnh \{#customization-options\}
Bạn có các tùy chọn tùy chỉnh sau trong trình tạo:
- Tab **Styles**: Điều chỉnh giao diện của thành phần.
- Tab **Element**: Đặt các thuộc tính của thành phần, như khả năng hiển thị, hành động khi nhấn nút hoặc các thuộc tính khác không liên quan đến giao diện của thành phần.
- Tab **Screen**: Thiết lập cấu hình màn hình chung, như tiêu đề hoặc hiển thị bộ đếm màn hình.
## Sao chép màn hình và thành phần \{#copy-screens-and-elements\}
Nếu bạn đã tạo một onboarding và muốn tái sử dụng một phần của nó, hoặc nếu bạn muốn thực hiện các thay đổi nhỏ và chạy A/B test, bạn có thể sao chép một hoặc nhiều màn hình từ onboarding này sang onboarding khác.
Để sao chép màn hình, mở onboarding builder và thực hiện một trong các cách sau:
- Nhấp chuột phải vào một màn hình và chọn **Copy**
- Chọn màn hình mong muốn và nhấn `Ctrl+C` (Windows) hoặc `⌘+C` (Mac)
Bạn cũng có thể sao chép các thành phần hoặc khối văn bản riêng lẻ, trong cùng một onboarding hoặc giữa các onboarding khác nhau.
## Sao chép màn hình từ web-to-app funnel \{#copy-screens-from-web-to-app-funnels\}
Nếu bạn sử dụng các web-to-app funnel được tạo trong [FunnelFox](https://funnelfox.com/) và muốn sử dụng các màn hình từ funnel trong onboarding, bạn có thể thực hiện nhanh chóng bằng cách sao chép màn hình trong funnel builder và dán vào onboarding builder:
1. Trong FunnelFox funnel builder, nhấp chuột phải vào một màn hình và chọn **Copy**, hoặc chọn màn hình và nhấn `Ctrl+C`/`⌘+C`.
2. Mở onboarding builder.
3. Nhấp chuột phải vào màn hình mà bạn muốn chèn màn hình đã sao chép và chọn **Paste**, hoặc chọn nó và nhấn `Ctrl+V`/`⌘+V`. Màn hình đã sao chép sẽ được chèn bên dưới màn hình đang chọn.
---
# File: adapty-paywall-builder
---
---
title: "Adapty Paywall Builder (Legacy)"
description: "Tạo paywall và onboarding flow bằng trình chỉnh sửa trực quan không cần code."
---
:::warning
Paywall Builder vẫn hoạt động đầy đủ, nhưng Adapty không còn bổ sung tính năng hay cập nhật cho nó nữa. Với các dự án mới, hãy cân nhắc sử dụng [Adapty Flow Builder](adapty-flow-builder) — trình chỉnh sửa trực quan không cần code dành cho paywall một màn hình và onboarding flow nhiều màn hình, hiển thị trực tiếp trên thiết bị:
- **Mọi loại flow**: Xây dựng paywall một màn hình, onboarding nhiều bước có kèm paywall, và bất cứ thứ gì ở giữa.
- **Hiển thị gốc (native)**: Flow hiển thị qua Adapty SDK, không dùng web view.
- **Cập nhật không cần phát hành lại**: Thay đổi nội dung, thiết kế hoặc logic bất cứ lúc nào — người dùng nhận cập nhật mà không cần app release mới.
:::
**Paywall Builder** của Adapty là công cụ trực quan không cần code để thiết kế paywall tùy chỉnh. Bạn có thể bắt đầu từ template, tùy chỉnh bố cục và thêm các thành phần như carousel, card, danh sách sản phẩm và footer. Builder cũng hỗ trợ font tùy chỉnh, tag sản phẩm và bản địa hóa.
Paywall Builder yêu cầu Adapty SDK v3.0 trở lên. Sau khi thiết kế xong paywall, hãy [thêm vào placement](add-audience-paywall-ab-test) và hiển thị trong ứng dụng của bạn:
- [iOS](ios-quickstart-paywalls)
- [Android](android-quickstart-paywalls)
- [React Native](react-native-quickstart-paywalls)
- [Flutter](flutter-quickstart-paywalls)
- [Unity](unity-quickstart-paywalls)
- [Capacitor](capacitor-quickstart-paywalls)
- [Kotlin Multiplatform](kmp-quickstart-paywalls)
---
# File: flutterflow
---
---
title: "Adapty Plugin for FlutterFlow"
description: "Tích hợp FlutterFlow với Adapty để quản lý gói đăng ký hiệu quả hơn."
---
Adapty là một nền tảng đa năng được thiết kế để giúp các ứng dụng di động phát triển. Dù bạn mới bắt đầu hay đã có hàng nghìn người dùng, Adapty giúp bạn tiết kiệm hàng tháng khi tích hợp in-app purchase và tăng gấp đôi doanh thu gói đăng ký nhờ quản lý paywall.
Plugin Adapty cho FlutterFlow cho phép bạn tận dụng toàn bộ tính năng của Adapty mà không cần viết code. Bạn có thể thiết kế trang paywall trong FlutterFlow, bật tính năng mua hàng cho chúng, rồi kiểm soát từ xa các sản phẩm hiển thị — bao gồm nhắm mục tiêu theo nhóm người dùng cụ thể hoặc A/B test. Sau khi phát hành ứng dụng, bạn có thể truy cập ngay số liệu phân tích chi tiết về giao dịch mua của khách hàng trực tiếp trên dashboard của chúng tôi.
Muốn cập nhật sản phẩm hiển thị trên paywall? Rất đơn giản! Chỉ cần thay đổi vài thao tác trong Adapty Dashboard, và khách hàng của bạn sẽ thấy sản phẩm mới ngay lập tức — không cần phát hành phiên bản ứng dụng mới!
Những gì Adapty còn mang lại cho bạn:
- **Gói đăng ký và In-App Purchase**: Adapty xử lý xác thực biên lai phía server cho bạn và đồng bộ khách hàng trên mọi nền tảng, kể cả web.
- **A/B test cho paywall**: Kiểm tra các mức giá, thời hạn, thời gian dùng thử và các yếu tố hiển thị khác nhau để tối ưu hóa gói đăng ký và sản phẩm mua một lần.
- **Phân tích mạnh mẽ**: Truy cập các chỉ số chi tiết để hiểu rõ hơn và cải thiện khả năng kiếm tiền của ứng dụng.
- **Tích hợp**: Adapty kết nối liền mạch với các công cụ phân tích của bên thứ ba như Amplitude, AppsFlyer, Adjust, Branch, Mixpanel, Facebook Ads, AppMetrica, Webhook tùy chỉnh và nhiều hơn nữa.
---
# File: ff-getting-started
---
---
title: "Bắt đầu"
description: "Bắt đầu với Adapty Feature Flags để cá nhân hóa các luồng đăng ký."
---
Với Adapty, bạn có thể tạo và chạy các paywall cũng như A/B test tại các điểm khác nhau trong hành trình người dùng ứng dụng di động của mình, chẳng hạn như Onboarding, Settings, v.v. Những điểm này được gọi là [Placements](placements). Một placement trong ứng dụng của bạn có thể quản lý nhiều paywall hoặc [A/B test](ab-tests) cùng một lúc, mỗi cái được tạo cho một nhóm người dùng nhất định mà chúng tôi gọi là [Audiences](audience). Hơn nữa, bạn có thể thử nghiệm với các paywall, thay thế cái này bằng cái khác theo thời gian mà không cần phát hành phiên bản ứng dụng mới. Thứ duy nhất bạn hardcode trong ứng dụng di động là placement ID.
Thư viện Adapty giữ cho paywall của bạn luôn được cập nhật với các sản phẩm mới nhất từ Adapty Dashboard. Nó [lấy dữ liệu sản phẩm](ff-action-flow) và [hiển thị trên paywall của bạn](ff-add-variables-to-paywalls), [xử lý các giao dịch mua](ff-make-purchase), và [kiểm tra mức độ truy cập của người dùng](ff-check-subscription-status) để xem họ có nên nhận được nội dung trả phí hay không.
Để bắt đầu, chỉ cần [thêm thư viện Adapty](ff-getting-started#add-the-adapty-library-as-a-dependency) vào dự án FlutterFlow của bạn và [khởi tạo nó](ff-getting-started#initiate-adapty-plugin) như hướng dẫn bên dưới.
:::warning
Trước khi bắt đầu, hãy lưu ý các hạn chế sau:
- Thư viện Adapty cho FlutterFlow không hỗ trợ ứng dụng web. Tránh biên dịch ứng dụng web với thư viện này.
- Thư viện Adapty cho FlutterFlow không hỗ trợ các paywall được tạo bằng Adapty Paywall Builder. Bạn cần tự thiết kế paywall của mình trong FlutterFlow trước khi kích hoạt mua hàng với Adapty.
:::
## Thêm thư viện Adapty làm dependency \{#add-the-adapty-library-as-a-dependency\}
1. Trong [FlutterFlow Dashboard](https://app.flutterflow.io/dashboard), mở dự án của bạn, sau đó nhấp vào **Settings and Integrations** từ menu bên trái. Trong phần **Project setup** ở bên trái, chọn **Project dependencies**.
2. Trong phần **FlutterFlow Libraries**, nhấp vào **Add Library** và nhập `adapty-xtuel0`. Nhấp vào **Add**.
3. Bây giờ, bạn cần liên kết SDK key của mình với thư viện. Nhấp vào **View details** bên cạnh thư viện.
4. Sao chép **Public SDK key** từ tab [**App Settings** -> **General**](https://app.adapty.io/settings/general) trong Adapty Dashboard.
5. Dán key vào **AdaptyApiKey** trong FlutterFlow.
Thư viện Adapty FF giờ đây sẽ được thêm vào dự án của bạn như một dependency. Trong cửa sổ thư viện **Adapty** FF, bạn sẽ tìm thấy tất cả các tài nguyên Adapty đã được import vào dự án của bạn.
## Gọi action kích hoạt mới khi khởi động ứng dụng \{#call-the-new-activation-action-at-application-launch\}
1. Đi đến phần **Custom Code** từ menu bên trái và mở `main.dart`.
2. Nhấp vào **+** và chọn `activate (Adapty)`.
3. Nhấp vào **Save**.
## Khởi tạo plugin Adapty \{#initiate-adapty-plugin\}
Để Adapty Dashboard nhận ra ứng dụng của bạn, bạn cần cung cấp một key đặc biệt trong FlutterFlow.
1. Trong dự án FlutterFlow của bạn, đi đến **Settings and Integrations > Permissions** từ menu bên trái.
2. Trong cửa sổ **Permissions** vừa mở, nhấp vào nút **Add Permission**.
3. Trong cả hai trường **iOS Permission Key** và **Android Permission Key**, dán `AdaptyPublicSdkKey`.
4. Đối với **Permission Message**, sao chép **Public SDK key** từ tab [**App Settings** -> **General**](https://app.adapty.io/settings/general) trong Adapty Dashboard. Mỗi ứng dụng có SDK key riêng, vì vậy nếu bạn có nhiều ứng dụng, hãy đảm bảo lấy đúng key.
Sau khi hoàn thành các bước này, bạn sẽ có thể gọi paywall trong ứng dụng FlutterFlow của mình và kích hoạt mua hàng thông qua đó.
## Tiếp theo là gì? \{#whats-next\}
1. [Tạo một action flow](ff-action-flow) để xử lý các sản phẩm paywall Adapty và dữ liệu của chúng trong FlutterFlow.
2. [Ánh xạ dữ liệu nhận được vào paywall](ff-add-variables-to-paywalls) mà bạn đã thiết kế trong FlutterFlow.
3. [Thiết lập nút mua hàng](ff-make-purchase) trên paywall của bạn để xử lý các giao dịch thông qua Adapty khi được nhấp.
4. Cuối cùng, [thêm kiểm tra trạng thái gói đăng ký](ff-check-subscription-status) để xác định có nên hiển thị nội dung trả phí cho người dùng hay không.
---
# File: ff-action-flow
---
---
title: "Bước 1. Tạo flow để hiển thị dữ liệu paywall"
description: "Thiết lập các action flow cho feature flag trong Adapty để cá nhân hóa hành trình đăng ký của người dùng."
---
:::important
Khi sử dụng plugin FlutterFlow, bạn không thể dùng các paywall được tạo trong Adapty Paywall Builder. Bạn phải tự xây dựng trang paywall của mình trong FlutterFlow và kết nối nó với Adapty.
:::
Sau khi thêm thư viện Adapty làm dependency cho dự án FlutterFlow, đã đến lúc xây dựng flow **lấy dữ liệu paywall và sản phẩm từ Adapty rồi hiển thị trên paywall bạn đã thiết kế trong FlutterFlow**.
Trước tiên, chúng ta cần nhận dữ liệu paywall từ Adapty. Chúng ta sẽ bắt đầu bằng cách yêu cầu paywall của Adapty, sau đó lấy các sản phẩm liên quan, và cuối cùng kiểm tra xem dữ liệu đã được nhận thành công chưa. Nếu thành công, chúng ta sẽ hiển thị tiêu đề sản phẩm và giá trên trang paywall. Ngược lại, chúng ta sẽ hiển thị thông báo lỗi.
Trước khi tiếp tục, hãy đảm bảo bạn đã hoàn thành các bước sau:
1. [Tạo ít nhất một paywall và thêm ít nhất một sản phẩm vào đó](create-paywall) trong Adapty Dashboard.
2. [Tạo ít nhất một placement](create-placement) và [thêm paywall của bạn vào đó](add-audience-paywall-ab-test) trong Adapty Dashboard.
Hãy bắt đầu!
## Bước 1.1. Yêu cầu paywall từ Adapty \{#step-11-request-adapty-paywall\}
Như đã đề cập, để hiển thị dữ liệu trên paywall FlutterFlow, trước tiên chúng ta cần lấy dữ liệu đó từ Adapty. Bước đầu tiên là lấy chính paywall của Adapty. Đây là cách thực hiện:
1. Mở màn hình paywall của bạn và chuyển sang phần **Actions** ở thanh bên phải. Tại đó, mở **Action Flow Editor**.
2. Trong cửa sổ **Select Action Trigger**, chọn **On Page Load**.
3. Nhấn **Add Action**. Sau đó, tìm kiếm custom action `getPaywall` và chọn nó.
4. Trong phần **Set Actions Arguments**, nhập ID thực của [placement bạn đã tạo](create-placement) trong Adapty Dashboard có chứa paywall. Trong ví dụ này là `monthly`. Hãy chắc chắn sử dụng ID placement thực của bạn!
5. Nếu bạn đã [bản địa hóa](localizations-and-locale-codes) paywall của mình trong Adapty dashboard, bạn cũng có thể thiết lập tham số **locale**.
6. Trong **Action Output Variable Name**, tạo một biến mới và đặt tên là `getPaywallResult`. Chúng ta sẽ dùng biến này ở bước tiếp theo để tham chiếu đến paywall Adapty và yêu cầu các sản phẩm của nó.
## Bước 1.2. Yêu cầu sản phẩm từ paywall Adapty \{#step-12-request-adapty-paywall-products\}
Tuyệt! Chúng ta đã lấy được paywall Adapty. Bây giờ, hãy lấy các sản phẩm liên quan đến paywall này:
1. Nhấn **+** bên dưới action vừa tạo và chọn **Add Action**. Action này sẽ nhận các sản phẩm từ paywall Adapty. Để làm điều này, tìm kiếm và chọn `getPaywallProducts`.
2. Trong phần **Set Actions Arguments**, chọn biến `getPaywallResult` đã tạo trước đó.
3. Điền vào các trường khác như sau:
- **Available Options**: Data Structured Field
- **Select Field**: value
- **Available Options**: No further changes
4. Nhấn **Confirm**.
5. Trong **Action Output Variable Name**, tạo một biến mới và đặt tên là `getPaywallProductsResult`. Chúng ta sẽ dùng biến này để liên kết paywall bạn đã thiết kế trong FlutterFlow với dữ liệu paywall Adapty.
## Bước 1.3. Thêm kiểm tra xem paywall đã tải thành công chưa \{#step-13-add-check-if-the-paywall-uploaded-successfully\}
Trước khi tiếp tục, hãy xác minh rằng paywall Adapty đã được nhận thành công. Nếu có, chúng ta có thể cập nhật paywall với dữ liệu sản phẩm. Nếu không, chúng ta sẽ xử lý lỗi. Đây là cách thêm kiểm tra:
1. Nhấn **+** và nhấn **Add Conditional**.
2. Trong phần **Action Output**, chọn biến output của action đã tạo trước đó (`getPaywallResult` trong ví dụ của chúng ta).
3. Để xác minh rằng paywall Adapty đã được nhận, kiểm tra sự hiện diện của một trường có giá trị. Điền vào các trường như sau:
- **Available Options**: Has Field
- **Field (AdaptyGetPaywallResult)**: value
4. Nhấn **Confirm** để hoàn tất điều kiện.
## Bước 1.4. Ghi log lượt xem paywall \{#step-14-log-the-paywall-review\}
Để đảm bảo analytics của Adapty theo dõi lượt xem paywall, chúng ta cần ghi log sự kiện này. Nếu không có bước này, lượt xem sẽ không được tính trong analytics. Đây là cách thực hiện:
1. Nhấn **+** bên dưới nhãn **TRUE** và nhấn **Add Action**.
2. Trong trường **Select Action**, tìm kiếm và chọn **logShowPaywall**.
3. Nhấn **Value** trong khu vực **Set Action Arguments** và chọn biến `getPaywallResult` chúng ta đã tạo. Biến này chứa dữ liệu paywall.
4. Điền vào các trường như sau:
- **Available Options**: Data Structured Field
- **Select Field**: value
5. Nhấn **Confirm**.
## Bước 1.5. Hiển thị lỗi nếu không nhận được paywall \{#step-15-show-error-if-paywall-not-received\}
Nếu paywall Adapty không được nhận, bạn cần [xử lý lỗi](error-handling-on-flutter-react-native-unity#system-storekit-codes). Trong ví dụ này, chúng ta sẽ chỉ đơn giản hiển thị một thông báo cảnh báo.
1. Thêm action **Informational Dialog** vào nhãn **FALSE**.
2. Trong trường **Title**, thêm văn bản bạn muốn hiển thị làm tiêu đề dialog. Trong ví dụ này là **Error**.
3. Nhấn **Value** trong ô **Message**.
4. Điền vào các trường như sau:
- **Set Variable**: biến `getPaywallProductResult` chúng ta đã tạo
- **Available Options**: Data Structure Field
- **Select Field**: error
- **Available Options**: Data Structure Field
- **Select Field**: errorMessage
5. Nhấn **Confirm**.
6. Thêm action **Terminate** vào flow **FALSE**.
7. Nhấn **Close** ở góc trên bên phải.
Chúc mừng! Bạn đã nhận thành công dữ liệu sản phẩm. Bây giờ, hãy [liên kết nó với paywall bạn đã thiết kế trong FlutterFlow](ff-add-variables-to-paywalls).
---
# File: ff-add-variables-to-paywalls
---
---
title: "Bước 2. Thêm dữ liệu vào trang paywall"
description: "Thêm biến Feature Flag vào paywall trong Adapty."
---
Sau khi đã [nhận được toàn bộ dữ liệu sản phẩm cần thiết](ff-action-flow), đã đến lúc ánh xạ chúng vào paywall đẹp mắt mà bạn đã thiết kế trong FlutterFlow. Trong ví dụ này, chúng ta sẽ ánh xạ tiêu đề sản phẩm và giá của nó.
## Bước 2.1. Thêm tiêu đề sản phẩm vào trang paywall \{#step-21-add-product-title-to-paywall-page\}
1. Nhấp đúp vào văn bản sản phẩm trên trang paywall của bạn. Trong cửa sổ **Set from Variable**, tìm kiếm biến `getPaywallProductResult` và chọn nó.
2. Điền vào các trường như sau:
- **Available Options**: Data Structured Field
- **Select Field**: value
- **Available Options**: Item at Index
- **List Index Options**: First
- **Available Options**: Data Structured Field
- **Select Field**: localizedTitle
- **Default Variable Value**: null
- **UI Builder Display Value**: Tùy ý, trong ví dụ là `product.title`
3. Nhấp **Confirm** để lưu các thay đổi.
## Bước 2.2. Thêm văn bản giá vào trang paywall \{#step-22-add-price-text-to-paywall-page\}
Lặp lại các bước từ Bước 2.1 cho văn bản giá như hướng dẫn bên dưới:
1. Nhấp đúp vào văn bản giá trên trang paywall của bạn. Trong cửa sổ **Set from Variable**, tìm kiếm biến `getPaywallProductResult` và chọn nó.
2. Điền vào các trường như sau:
- **Available Options**: Data Structured Field
- **Select Field**: value
- **Available Options**: Item at Index
- **List Index Options**: First
- **Available Options**: Data Structured Field
- **Select Field**: price
- **Default Variable Value**: null
- **UI Builder Display Value**: Tùy ý, trong ví dụ là `product.price`
3. Nhấp nút **Confirm** để lưu các thay đổi.
### Thêm giá theo đơn vị tiền tệ địa phương vào trang paywall \{#add-price-in-local-currency-to-paywall-page\}
1. Nhấp đúp vào giá trên trang paywall của bạn. Trong cửa sổ **Set from Variable**, tìm kiếm biến `getPaywallProductResult` và chọn nó.
2. Điền vào các trường như sau:
- **Available Options**: Data Structured Field
- **Select Field**: value
- **Available Options**: Item at Index
- **List Index Options**: First
- **Available Options**: Data Structured Field
- **Select Field**: price
- **Available Options**: Data Structured Field
- **Select Field**: amount
- **Available Options**: Decimal
- **Decimal Type**: Automatic
- **Default Variable Value**: null
- **UI Builder Display Value**: Tùy ý, trong ví dụ là `price.amount`
3. Nhấp **Confirm** để lưu các thay đổi.
Và thế là xong! Khi bạn khởi chạy ứng dụng, nó sẽ hiển thị dữ liệu sản phẩm từ paywall Adapty trực tiếp trên trang paywall của bạn!
Bây giờ hãy [cho phép người dùng mua sản phẩm này](ff-make-purchase).
---
# File: ff-make-purchase
---
---
title: "Bước 3. Kích hoạt tính năng mua hàng"
description: "Tìm hiểu cách thực hiện mua hàng bằng hệ thống Feature Flags của Adapty."
---
Chúc mừng! Bạn đã [thiết lập thành công paywall để hiển thị dữ liệu sản phẩm từ Adapty](ff-add-variables-to-paywalls), bao gồm tên và giá sản phẩm.
Bây giờ, hãy chuyển sang bước cuối cùng – cho phép người dùng thực hiện mua hàng qua paywall.
## Bước 3.1. Cho phép người dùng mua hàng \{#step-31-enable-users-to-make-purchases\}
1. Nhấp đúp vào nút mua trên trang paywall của bạn. Trong bảng bên phải, mở phần **Actions** nếu chưa mở.
2. Mở **Action Flow Editor**.
3. Trong cửa sổ **Select Action Trigger**, chọn **On Tap**.
4. Trong cửa sổ **No Actions Created**, nhấp **Add Action**. Tìm kiếm action `makePurchase` và chọn nó.
5. Trong phần **Set Actions Arguments**, chọn biến `getPaywallProductsResult` đã tạo trước đó.
6. Điền vào các trường như sau:
- **Available Options**: Data Structure Field
- **Select Field**: value
- **Available Options**: Item at Index
- **List Index Options**: First
7. Nhấp vào `subscriptionUpdateParameters`, tìm kiếm `AdaptySubscriptionUpdateParameters` và chọn nó. Nhấp **Confirm**.
:::info
Theo mặc định, bạn có thể để trống tất cả các trường của object. Bạn chỉ cần điền vào khi muốn thay thế một gói đăng ký bằng gói khác trong ứng dụng Android. Đọc thêm [tại đây](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/).
:::
8. Nhấp **Confirm**.
9. Trong **Action Output Variable Name**, tạo một biến mới và đặt tên là `makePurchaseResult` – biến này sẽ được dùng sau để xác nhận mua hàng thành công.
## Bước 3.2. Kiểm tra xem mua hàng có thành công không \{#step-32-check-if-the-purchase-was-successful\}
Bây giờ, hãy thiết lập kiểm tra xem giao dịch mua có được thực hiện thành công không.
1. Nhấp **+** và nhấp **Add Conditional**.
2. Trong **Set Condition for Action**, chọn biến `makePurchaseResult`.
3. Trong cửa sổ **Set Variable**, điền vào các trường như sau:
- **Available Options**: Has Field
- **Select Field**: profile
4. Nhấp **Confirm**.
## Bước 3.3. Mở nội dung trả phí \{#step-33-open-paid-content\}
Nếu mua hàng thành công, bạn có thể mở khóa nội dung trả phí. Cách thiết lập như sau:
1. Nhấp **+** dưới nhãn **TRUE** và nhấp **Add Action**.
2. Trong trường **Define Action**, tìm kiếm và chọn trang bạn muốn mở từ danh sách **Navigate To**. Trong ví dụ này, trang đó là **Questions**.
## Bước 3.4. Hiển thị thông báo lỗi nếu mua hàng thất bại \{#step-34-show-error-message-if-purchase-failed\}
Nếu mua hàng thất bại, hãy hiển thị thông báo cho người dùng.
1. Thêm action **Informational Dialog** vào nhãn **FALSE**.
2. Trong trường **Title**, nhập văn bản bạn muốn dùng làm tiêu đề hộp thoại, chẳng hạn **Purchase Failed**.
3. Nhấp **Value** trong hộp **Message**. Trong cửa sổ **Set from Variable**, tìm kiếm `makePurchaseResult` và chọn nó. Điền vào các trường như sau:
- **Available Options**: Data Structure Field
- **Select Field**: error
- **Available Options**: Data Structure Field
- **Select Field**: errorMessage
4. Nhấp **Confirm**.
5. Thêm action **Terminate** vào flow **FALSE**.
6. Cuối cùng, nhấp **Close** ở góc trên bên phải.
Chúc mừng! Người dùng của bạn giờ đây có thể mua sản phẩm. Để hoàn thiện hơn, hãy [thiết lập kiểm tra quyền truy cập nội dung trả phí](ff-check-subscription-status) ở các nơi khác để quyết định hiển thị nội dung trả phí hay paywall cho họ.
---
# File: ff-check-subscription-status
---
---
title: "Bước 4. Kiểm tra quyền truy cập nội dung trả phí"
description: "Tìm hiểu cách kiểm tra trạng thái gói đăng ký bằng feature flags của Adapty để phân khúc người dùng tốt hơn."
---
Khi xác định xem người dùng có quyền truy cập nội dung trả phí cụ thể hay không, bạn cần kiểm tra mức độ truy cập của họ. Điều này có nghĩa là kiểm tra xem người dùng có ít nhất một mức độ truy cập và mức đó có phải là mức cần thiết hay không.
Bạn có thể làm điều này bằng cách kiểm tra hồ sơ người dùng, trong đó chứa tất cả các mức độ truy cập hiện có.
Bây giờ, hãy cho phép người dùng mua sản phẩm của bạn:
1. Double-click vào nút sẽ hiển thị nội dung trả phí và mở phần **Actions** ở khung bên phải nếu chưa mở.
2. Mở **Action Flow Editor**.
3. Trong cửa sổ **Select Action Trigger**, chọn **On Tap**.
4. Trong cửa sổ **No Actions Created**, nhấp vào nút **Add Conditional Action**.
5. Nhấp vào **UNSET** để đặt các đối số hành động và chọn biến `currentProfile`. Đây là biến Adapty chứa dữ liệu về hồ sơ người dùng hiện tại.
6. Điền vào các trường như sau:
- **Available Options**: Data Structure Field
- **Select Field**: accessLevels
- **Available Options**: Filter List Items
- **Filter Conditions**:
1. Chọn **Conditions -> Single Condition** và nhấp vào **UNSET**.
2. Trong trường **First value**, chọn **Item in list** làm **Source** và điền vào các trường như sau:
- **Available Options**: Data Structure Field
- **Select Field**: accessLevelIdentifier
3. Đặt toán tử lọc thành **Equal to**.
4. Nhấp vào **UNSET** bên cạnh **Second value** và trong trường **Value**, nhập ID mức độ truy cập của bạn; trong ví dụ của chúng tôi, chúng tôi dùng `premium`.
5. Nhấp **Confirm** và tiếp tục điền vào các trường khác bên dưới.
- **Available Options**: Item at Index
- **List Index Options**: First
- **Available Options**: Data Structure Field
- **Select Field**: accessLevel
- **Available Options**: Data Structure Field
- **Select Field**: isActive
7. Nhấp **Confirm**.
Bây giờ, hãy thêm các hành động cho những gì xảy ra tiếp theo — nếu người dùng có gói đăng ký phù hợp hay không. Đưa họ đến trang dành cho người đăng ký premium hoặc mở trang paywall để họ có thể mua quyền truy cập.
---
# File: ff-resources
---
---
title: "Các action và kiểu dữ liệu của plugin Adapty FlutterFlow"
description: "Truy cập tài nguyên cờ tính năng của Adapty để tối ưu hóa các tính năng dựa trên gói đăng ký."
---
## Hành động tùy chỉnh \{#custom-actions\}
Dưới đây là các phương thức Adapty được tích hợp vào FlutterFlow thông qua plugin Adapty. Chúng có thể được sử dụng như các hành động tùy chỉnh trong FlutterFlow.
| Custom Action | Mô tả | Tham số hành động | Kiểu dữ liệu Adapty - Biến đầu ra |
|---|----|--------|----|
| activate | Khởi tạo Adapty SDK | Không có ||
| getPaywall
| Truy xuất một paywall. Hành động này không trả về sản phẩm của paywall. Sử dụng hành động `getPaywallProducts` để lấy các sản phẩm thực tế |getPaywallProducts
| Trả về danh sách các sản phẩm thực tế của paywall | [AdaptyPaywall](ff-resources#adaptypaywall) | [AdaptyGetProductsResult](ff-resources#adaptygetproductsresult) | |getProductsIntroductoryOfferEligibility
| Kiểm tra xem người dùng có đủ điều kiện nhận ưu đãi giới thiệu cho gói đăng ký iOS hay không | [AdaptyPaywallProduct](product) | [AdaptyGetIntroEligibilitiesResult](ff-resources#adaptygetintroeligibilitiesresult) | |makePurchase
| Hoàn tất giao dịch mua và mở khóa nội dung. Nếu paywall có ưu đãi, Adapty tự động áp dụng khi thanh toán |getProfile
|Truy xuất hồ sơ người dùng hiện tại của ứng dụng. Cho phép bạn thiết lập mức độ truy cập và các thông số khác
Nếu thất bại (ví dụ: do không có kết nối internet), dữ liệu đã lưu trong bộ nhớ đệm sẽ được trả về. Adapty thường xuyên cập nhật bộ nhớ đệm hồ sơ để đảm bảo thông tin luôn được cập nhật nhất có thể
| Không có | [AdaptyGetProfileResult](ff-resources#adaptygetprofileresult) | | updateProfile | Thay đổi các thuộc tính tùy chọn của hồ sơ người dùng hiện tại như email, số điện thoại, v.v. Bạn có thể dùng các thuộc tính này sau để tạo [phân khúc](segments) người dùng hoặc xem trong CRM | ID và các thông số cần cập nhật cho [AdaptyProfile](ff-resources#adaptyprofile) | [AdaptyError](ff-resources#adaptyerror) (Tùy chọn) | | restorePurchases | Khôi phục tất cả các giao dịch mua mà người dùng đã thực hiện | Không có | [AdaptyGetProfileResult](ff-resources#adaptygetprofileresult) | | logShowPaywall | Ghi lại khi một paywall cụ thể được hiển thị cho người dùng | [AdaptyPaywall](ff-resources#adaptypaywall) | [AdaptyError](ff-resources#adaptyerror) (Tùy chọn) | | identify | Xác định người dùng bằng `customerUserId` của hệ thống của bạn | customerUserId | [AdaptyError](ff-resources#adaptyerror) (Tùy chọn) | | logout | Đăng xuất người dùng hiện tại khỏi ứng dụng của bạn | Không có | [AdaptyError](ff-resources#adaptyerror) (Tùy chọn)| | presentCodeRedemptionSheet | Hiển thị một sheet cho phép người dùng đổi mã (chỉ dành cho iOS) | Không có | Không có | ## Các kiểu dữ liệu \{#data-types\} Các kiểu dữ liệu của Adapty (tập hợp các giá trị dữ liệu) được cung cấp đến FlutterFlow thông qua plugin Adapty. ### AdaptyAccessLevel Thông tin về [mức độ truy cập](access-level) của người dùng. | Tên trường | Kiểu | Mô tả | |--------------------------|----------|-------------| | activatedAt | DateTime | Thời điểm mức độ truy cập này được kích hoạt | | activeIntroductoryOfferType | String | Loại ưu đãi giới thiệu đang áp dụng. Nếu được đặt, nghĩa là một ưu đãi đã được áp dụng trong chu kỳ đăng ký này | | activePromotionalOfferId | String | ID của ưu đãi đang áp dụng (mua từ iOS) | | activePromotionalOfferType | String | Loại ưu đãi đang áp dụng (mua từ iOS). Nếu được đặt, nghĩa là một ưu đãi đã được áp dụng trong chu kỳ đăng ký này | | billingIssueDetectedAt | DateTime | Thời điểm phát hiện sự cố thanh toán. Gói đăng ký vẫn có thể đang hoạt động. Đặt về null nếu thanh toán được xử lý thành công | | cancellationReason | String | Lý do hủy gói đăng ký | | expiresAt | DateTime | Thời điểm hết hạn của mức độ truy cập (có thể đã qua hoặc không được đặt đối với quyền truy cập trọn đời) | | id | String | Định danh của mức độ truy cập | | isActive | Boolean | True nếu mức độ truy cập này đang hoạt động. Nhìn chung, bạn có thể kiểm tra thuộc tính này để xác định xem người dùng có quyền truy cập vào các tính năng premium hay không | | isInGracePeriod | Boolean | True nếu gói đăng ký tự động gia hạn này đang trong [thời gian ân hạn](https://developer.apple.com/help/app-store-connect/manage-subscriptions/enable-billing-grace-period-for-auto-renewable-subscriptions) | | isLifetime | Boolean | True nếu mức độ truy cập này có hiệu lực trọn đời (không có ngày hết hạn) | | isRefund | Boolean | True nếu giao dịch mua này đã được hoàn tiền | | offerId | String | ID của ưu đãi đang áp dụng (mua từ Android) | | renewedAt | DateTime | Thời điểm mức độ truy cập được gia hạn lần cuối | | startsAt | DateTime | Thời điểm bắt đầu của mức độ truy cập này (có thể là trong tương lai) | | store | String | Cửa hàng nơi thực hiện giao dịch mua | | unsubscribedAt | DateTime | Thời điểm tắt tự động gia hạn cho gói đăng ký. Gói đăng ký vẫn có thể đang hoạt động. Nếu không được đặt, người dùng đã kích hoạt lại gói đăng ký | | vendorProductId | String | ID sản phẩm từ cửa hàng đã mở khóa mức độ truy cập này | | willRenew | Boolean | True nếu gói đăng ký tự động gia hạn này được đặt để gia hạn | ### AdaptyAccessLevelIdentifiers Struct này được dùng để thay thế cặp key-value cho `Map
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. Vào 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 `"YOUR_PUBLIC_SDK_KEY"` trong code.
:::important
- Đảm bảo bạn dùng **Public SDK key** để khởi tạo Adapty, còn **Secret key** chỉ 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 chắc chắn 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 `
### 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.
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 Dictionarytù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 ) { } ```
## 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';
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: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';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." ---Đố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). |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.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 `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)
}
```
:::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
}
```
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