# REACT-NATIVE - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: vi Generated on: 2026-07-24T13:01:43.502Z Total files: 45 --- # File: sdk-installation-react-native-expo --- --- title: "Install & configure Adapty React Native SDK in an Expo project" description: "Step-by-step guide on installing Adapty React Native SDK in an Expo project for subscription-based apps." --- :::important Hướng dẫn này đề cập đến việc cài đặt và cấu hình Adapty React Native SDK **trong một dự án Expo**. Nếu bạn đang sử dụng **React Native thuần (không có 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 phải 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 giúp tạo paywall đa nền tảng dễ dàng. AdaptyUI được kích hoạt tự động cùng với module core. Nếu bạn muốn xem hướng dẫn đầy đủ về cách triển khai IAP trong ứng dụng React Native, hãy xem [tại đây](https://adapty.io/blog/react-native-in-app-purchases-tutorial/). :::tip Bạn 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) — đầy đủ chức năng bao gồm mua hàng thực tế 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ó thể xem video sau:
### 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.
:::
[Adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) tự động hóa quá trình tích hợp Adapty từ đầu đến cuối: thiết lập dashboard, cài đặt SDK, paywall và xác minh từng bước. Skill này tự động phát hiện nền tảng của bạn và lấy 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 form 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** — sử dụng [skills CLI](https://skills.sh) (lưu ý rằng các skill được cài đặt theo cách này sẽ không tự động cập nhật):
```
npx skills add adaptyteam/adapty-sdk-integration-skill
```
Ngoài ra, bạn có thể clone repo và sao chép `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 xong, 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.
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 thường 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ủa họ ra sao. 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 còn 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 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 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 paywall trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối mạng kém.
| | **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ị đã 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.
Đố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` được lấy từ `import com.adapty.utils.seconds`), hoặc `TimeInterval.seconds(5)`. Để không giới hạn thời gian chờ, hãy dùng `TimeInterval.INFINITE`.
| ## Tham số phản hồi \{#response-parameters\} | Tham số | Mô tả | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | Một đối tượng `AdaptyFlow` chứa các mã định danh của flow (`id`, `variationId`), tên, placement, các biến thể paywall (`paywalls`) và bất kỳ Remote Config nào (`remoteConfigs`). | ## Lấy cấu hình view \{#fetch-the-view-configuration\} :::important Hãy đảm bảo bật toggle **Show on device** trong 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. ::: Nếu placement được thiết kế trong **Flow Builder** hoặc **Paywall Builder**, Adapty sẽ tự động render giao diện cho bạn. Tạo view bằng `createFlowView`, sau đó [hiển thị flow hoặc paywall](react-native-present-paywalls). Nếu placement là một paywall tùy chỉnh không có giao diện Builder, hãy [xử lý nó như một paywall Remote Config](present-remote-config-paywalls-react-native). Trong React Native SDK, hãy gọi `createFlowView` trực tiếp — bạn không cần fetch cấu hình view trước. :::warning Kết quả của phương thức `createFlowView` chỉ có thể được sử dụng một lần. Nếu cần dùng lại, hãy gọi lại phương thức `createFlowView` từ đầu. Gọi hai lần mà không tạo lại có thể dẫn đến lỗi `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers try { const view = await createFlowView(flow); } catch (error) { // handle the error } ``` Tham số: | Tham số | Bắt buộc | Mô tả | | :------------------- | :------- | :----------------------------------------------------------- | | **flow** | bắt buộc | Đối tượng `AdaptyFlow` để lấy controller cho flow/paywall mong muốn. | | **customTags** | tùy chọn | Định nghĩa một dictionary các custom tag và giá trị tương ứng. Custom tag đóng vai trò là placeholder trong nội dung, được thay thế động bằng các chuỗi cụ thể để cá nhân hóa nội dung trong flow/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 thời điểm hiển thị sản phẩm trên màn hình. Khi đặt là `true`, AdaptyUI sẽ tự động tải các sản phẩm cần thiết. Mặc định: `false`. | | **android.enableSafeArea** | tùy chọn | Chỉ dành cho Android (bỏ qua trên iOS). Truyền dưới dạng đối tượng lồng nhau: `android: { enableSafeArea: true }`. Khi đặt là `true`, flow view sẽ áp dụng padding safe-area. Mặc định là `true` cho kiểu trình bày modal (`createFlowView` + `present()`) và `false` cho component `AdaptyFlowView` nhúng. Giá trị mặc định phù hợp với hầu hết các trường hợp. | :::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 flow](add-paywall-locale-in-adapty-paywall-builder) và cách sử dụng mã ngôn ngữ đúng cách [tại đây](react-native-localizations-and-locale-codes). ::: Sau khi có view, hãy [hiển thị flow/paywall](react-native-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ó 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ị 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ể sử dụng phương thức `getFlowForDefaultAudience`, phương thức này sẽ 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ư đã mô tả chi tiết trong phần [Lấy flow/paywall](#fetch-flowpaywall) ở trên. :::warning Lý do chúng tôi khuyến nghị sử dụng `getFlow` Phương thức `getFlowForDefaultAudience` có một số hạn chế đá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 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 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 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` như đã mô tả [ở trên](#fetch-flowpaywall). ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlowForDefaultAudience(id); // the requested flow/paywall } catch (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ừ server 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 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ó 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 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òn 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 flow/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 đế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 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 phát 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. ::: Dưới đây là ví dụ về cách cung cấp custom assets thông qua một dictionary đơn giản: ```javascript const customAssets: Recordtù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 subtag được phân tách bằng ký tự gạch ngang (**-**). 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 [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 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 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ể kết nối internet có kém đế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 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 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 server 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 paywall, đồng thời đảm bảo độ tin cậy ngay cả khi kết nối internet không ổn định.
| | **loadTimeoutMs** | mặc định: 5 giây |Giá trị này giới hạn thời gian timeout cho phương thức này. Nếu đạt đến giới hạn timeout, 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ị đã 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.
Đối với Android: Bạn có thể tạo `TimeInterval` bằng các extension function (như `5.seconds`, trong đó `.seconds` đến từ `import com.adapty.utils.seconds`), hoặc `TimeInterval.seconds(5)`. Để không giới hạn, hãy dùng `TimeInterval.INFINITE`.
| ## Tham số phản hồi \{#response-parameters\} | Tham số | Mô tả | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Đối tượng [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) chứa 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 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 `ViewConfiguration` hay không — đây là dấu hiệu cho biết paywall được tạo bằng Paywall Builder. Điều này sẽ giúp bạn xác định cách hiển thị paywall. Nếu có `ViewConfiguration`, 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 tự lấy view configuration 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 lại phương thức `createPaywallView` từ đầu. Việc 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 | Đối tượng `AdaptyPaywall` để lấy controller cho paywall mong muốn. | | **customTags** | tùy chọn | Định nghĩa một dictionary gồm các custom tag và giá trị tương ứ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. Xem thêm trong phần Custom tags in paywall builder để biết thêm chi tiết. | | **prefetchProducts** | tùy chọn | Bật tùy chọn này để tối ưu hóa thời điểm hiển thị sản phẩm trên màn hình. Khi đặt là `true`, AdaptyUI sẽ tự động tải 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, 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, 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 đợ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 cả. Để giải quyết vấn đề 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 đã 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 paywall bằng phương thức `getPaywall`, như đã trình bày 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ị dùng `getPaywall` Phương thức `getPaywallForDefaultAudience` có một số hạn chế đáng kể: - **Các vấn đề về khả năng 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ế 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 đang dùng 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 (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 chấp nhận những hạn chế đó để đổi lấy 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 tiếp tục 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` khả dụng 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 | 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 đị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ằng dấu trừ (**-**). Thẻ phụ đầu tiên là ngôn ngữ, thẻ 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ã ngôn ngữ](react-native-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 đã 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 kết nối 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 mạng không ổn định. 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 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 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 đế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 xem trước cục bộ trong khi hình ảnh chính từ remote đang tải. - Hiển thị hình ả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 React Native SDK lên phiên bản 3.8.0 trở lên. ::: Dưới đây là ví dụ về cách cung cấp custom assets thông qua một dictionary đơn giản: ```javascript const customAssets: Record
## 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 `logShowFlow` (React Native SDK v4+) / `logShowPaywall` trong code, khiến số lượt xem bị tính trùng nếu bạn đang dùng Paywall Builder hoặc Flow Builder. Với các flow và paywall được tạo bằng những công cụ này, 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 `logShowFlow` (React Native SDK v4+) / `logShowPaywall` trong code nếu đang sử dụng Paywall Builder hoặc Flow 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](react-native-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: react-native-quickstart-manual
---
---
title: "Kích hoạt mua hàng trong paywall tùy chỉnh của bạn với React Native SDK"
description: "Tích hợp Adapty SDK vào các paywall React Native tùy chỉnh của bạn để kích hoạ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. Toàn quyền kiểm soát việc triển khai paywall, trong khi Adapty SDK lấy sản phẩm, xử lý các 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 developer đang triển khai paywall tùy chỉnh.** Nếu bạn muốn cách đơn giản nhất để kích hoạt tính năng mua hàng, hãy sử dụng [Adapty Flow Builder](react-native-quickstart-paywalls). Với Flow Builder, bạn tạo flow 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\}
Để kích hoạt in-app purchase, bạn cần nắm vững ba khái niệm cốt lõi:
- [**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ấu hình 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 danh sách 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.
- [**Placement**](placements) – nơi và thời điểm hiển thị paywall trong ứng dụng của bạn (ví dụ: `main`, `onboarding`, `settings`). Bạn cài đặt 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 việc chạy A/B test và hiển thị các paywall khác nhau cho từng nhóm người dùng trở nên dễ dàng hơn.
Hãy đảm bảo bạn hiểu rõ các khái niệm này, dù bạn đang dùng paywall tùy chỉnh của riêng mình. Về cơ bản, đây chỉ là cách bạn quản lý các sản phẩm mà bạn 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**. Cài đặt này cho phép bạn lấy danh sách sản phẩm. Để hiểu những gì cần thực hiện trên dashboard, hãy xem hướng dẫn quickstart [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 danh sách sản phẩm \{#step-1-get-products\}
Để lấy danh sách sản phẩm cho paywall tùy chỉnh, bạn cần:
1. Lấy đối tượng `flow` bằng cách truyền ID [placement](placements) vào phương thức `getFlow`.
2. Lấy mảng sản phẩm cho flow này bằng phương thức `getPaywallProducts`.
```typescript showLineNumbers
async function loadPaywall() {
try {
const flow: AdaptyFlow = await adapty.getFlow('YOUR_PLACEMENT_ID');
const products: AdaptyPaywallProduct[] = await adapty.getPaywallProducts(flow);
// Use products to build your custom paywall UI
} catch (error) {
// Handle the error
}
}
```
## Bước 2. Xử lý thanh toán \{#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. Phương thức này sẽ xử lý flow thanh toán 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 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ử giao dịch mua 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\}
:::tip
Bạn có câu hỏi hoặc gặp sự cố? Hãy xem [diễn đàn hỗ trợ](https://adapty.featurebase.app/) của chúng tôi — nơi bạn có thể tìm câu trả lời cho các câu hỏi thường gặp hoặc đặt câu hỏi của riêng mình. Đội ngũ và cộng đồng của chúng tôi luôn sẵn sàng giúp đỡ!
:::
Paywall của bạn đã sẵn sàng để hiển thị trong ứng dụng. Hãy kiểm tra các giao dịch mua trong [sandbox App Store](test-purchases-in-sandbox) hoặc trong [Google Play Store](testing-on-android) để đảm bảo bạn có thể hoàn thành một giao dịch mua thử nghiệm từ paywall. Để xem cách hoạt động trong một triển khai sẵn sàng cho môi trường production, hãy xem [CustomPurchaseScreen.tsx](https://github.com/adaptyteam/AdaptySDK-React-Native/blob/master/examples/ExpoGoWebMock/src/CustomPurchaseScreen.tsx) trong ứng dụng mẫu của chúng tôi, nơi minh họa cách xử lý giao dịch mua với xử lý lỗi đúng cách, trạng thái tải, và quản lý trạng thái giao diện.
Tiếp theo, [kiểm tra xem người dùng đã hoàn tất giao dịch mua chưa](react-native-check-subscription-status) để quyết định hiển thị paywall hay cấp quyền truy cập vào các tính năng trả phí.
---
# File: fetch-paywalls-and-products-react-native
---
---
title: "Tải paywall và sản phẩm cho paywall remote config trong React Native SDK"
description: "Tải paywall và sản phẩm trong Adapty React Native SDK để tối ưu hóa việc kiếm tiền từ người dùng."
---
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 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 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ể kết nối internet 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 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 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 định kỳ 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 để tải flow và paywall nhanh hơn cùng một server dự phòng độc lập phòng 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 flow 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ị đã 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, `getFlow` không còn nhận tham số `locale` nữa. Đối với các paywall tùy chỉnh, tất cả các ngôn ngữ khả dụng đều được trả về trong Remote Config của flow (`flow.remoteConfigs`) — hãy chọn ngôn ngữ phù hợp với thiết bị hoặc cài đặt ngôn ngữ của người dùng. ::: Đừ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 (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 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 đó. 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. Tham số trả về: | Tham số | Mô tả | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Một đối tượng `AdaptyFlow` chứa placement, các định danh (`id`, `variationId`), tên, các biến thể paywall của nó (`paywalls`), và một mảng `remoteConfigs` (một mục cho mỗi locale đã cấu hình). Để lấy sản phẩm cho flow, hãy gọi `getPaywallProducts(flow)`. | ## Lấy danh sách 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ó: ```typescript showLineNumbers try { // ...flow const products = await adapty.getPaywallProducts(flow); // the requested products list } catch (error) { // handle the error } ``` Tham số phản hồi: | Tham số | Mô tả | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Danh sách các đối tượng [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) bao gồm: mã đị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 triển khai thiết kế paywall của riêng bạn, bạn có thể sẽ cần truy cập các thuộc tính sau 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 để 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ả | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Tiêu đề** | Để hiển thị tiêu đề của sản phẩm, dùng `product.localizedTitle`. Lưu ý rằng bản địa hóa này 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ị. | | **Giá** | Để 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 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 trả về 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`. | | **Chu kỳ gói đăng ký** | Để 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 ngôn ngữ của thiết bị. Để lấy chu kỳ gói đăng ký theo cách lập trình, dùng `product.subscription?.subscriptionPeriod`. Từ đó bạn có thể truy cập thuộc tính `unit` để lấy đơn vị thời gian (tức là `'day'`, `'week'`, `'month'`, `'year'` hoặc `'unknown'`). Giá trị `numberOfUnits` sẽ cho bạn biết số đơn vị trong mỗi chu kỳ. Ví dụ: với gói đăng ký theo quý, thuộc tính `unit` sẽ là `'month'` và `numberOfUnits` sẽ là `3`. | | **Ưu đãi giới thiệu** | Để hiển thị huy hiệu hoặc chỉ báo rằng 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: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 kết nối 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 mạng của họ có chập chờn đến đâu. 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à hoàn toàn 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 gỡ cài đặt ứng dụng hoặc thực hiện 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 cần là mã ngôn ngữ gồm một hoặc nhiều subtag được phân tách bằng ký tự dấu trừ (**-**). Subtag đầu tiên là mã ngôn ngữ, subtag thứ hai là mã vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).
Xem [Localizations và locale codes](react-native-localizations-and-locale-codes) để biết thêm thông tin về locale codes 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 gặp 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ể kết nối internet có kém đế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 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 ứng dụng được cài đặt lại hoặc đượ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](react-native-use-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 kém.
| | **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`, 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ì paywall được cấu hình từ xa, các sản phẩm hiển thị, 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 những 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 cũng nên 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ố trả về: | Tham số | Mô tả | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Đối tượng [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) bao gồm: 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 danh sách sản phẩm \{#fetch-products\} Sau khi có paywall, bạn có thể lấy danh sách 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ố trong kết quả trả về: | Tham số | Mô tả | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Danh sách các đối tượng [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) bao gồm: mã đị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 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 từ đối tượng [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct). Dưới đây là các thuộc tính được dùng phổ biến nhất — xem 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, 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, 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, dùng `product.price?.currencySymbol`. | | **Subscription Period** | Để hiển thị chu kỳ (ví dụ: tuần, tháng, năm,...), dùng `product.subscription?.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, dùng `product.subscription?.subscriptionPeriod`. Từ đó bạn có thể truy cập thuộc tính `unit` để lấy đơn vị thời gian (tức là `'day'`, `'week'`, `'month'`, `'year'` hoặc `'unknown'`). Giá trị `numberOfUnits` cho 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 nào đó 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`
|Mã đị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ẻ con phân cách bởi 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ã 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ừ 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ị 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 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 của họ 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 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 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 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\}Đố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ế độ Kids trong React Native SDK" description: "Dễ dàng bật Chế độ Kids để 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 sử dụng Adapty SDK, chỉ cần vài bước đơn giản để cấu hình SDK phù hợp với 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 ứng dụng. :::important Trên iOS, Chế độ Trẻ em được bật thông qua trait Swift package `KidsMode`, giúp loại bỏ toàn bộ code liên quan đến IDFA, AdSupport và AppTrackingTransparency. Tính năng này yêu cầu SDK v4 (cài đặt iOS SDK gốc qua Swift Package Manager) và **Xcode 26** trở lên. Xem [Cập nhật trong iOS Podfile của bạn](#updates-in-your-ios-podfile) bên dưới. ::: ## Cần cấu hình những gì? \{#whats-required\} Bạn cần cấu hình 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. User ID có định dạng `tùy chọn
mặc định: `en`
|Định danh bản địa hóa của onboarding. Tham số này là mã ngôn ngữ gồm một hoặc hai thẻ con được ngăn cách bởi 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ã 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 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 thường gặp 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ẽ tải nhanh hơn dù kết nối internet có kém đến đâu. Cache được cập nhật thường xuyên nên có thể 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 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ữ 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 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, đồng thời đả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`, vì thao tác có thể bao gồm nhiều yêu cầu khác nhau bên trong.
| Tham số phản hồi: | Tham số | Mô tả | |:--------|:------| | Onboarding | Một đối tượng [`AdaptyOnboarding`](https://react-native.adapty.io/interfaces/adaptyonboarding) 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 độ tải onboarding với onboarding mặc định cho đối tượng \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Thông thường, onboarding đượ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 độ 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 tải 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ị gì 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 sẽ 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ư đã trình bày 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 độ tải nhanh hơn quan trọng 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ư hướng dẫn 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); // onboarding được yêu cầu } catch (error) { // xử lý lỗi } ``` 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ần là mã ngôn ngữ gồm một hoặc hai thẻ con 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à 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ừ 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 thường 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 sẽ có tốc độ tải nhanh hơn dù kết nối internet không ổn định. Cache được cập nhật thường xuyên nê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òn 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ô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 để 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 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." --- :::warning **Onboardings đã bị deprecated trong SDK v4 và sẽ bị xóa trong một bản phát hành tương lai.** Chúng không còn nhận được các bản sửa lỗi hoặc cải tiến nữa. Hãy sử dụng [flows](react-native-get-pb-paywalls) thay thế: không giống như onboardings chạy bên trong WebView, flows render trực tiếp trên thiết bị — mang lại animation mượt mà hơn, giao diện native nhất quán, thời gian tải nhanh hơn, và không phụ thuộc vào WebView runtime. Xem [Lấy flows & paywalls](react-native-get-pb-paywalls) và [Hiển thị flows & paywalls](react-native-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 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ể 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** hay **Allow notifications**, event handler sẽ được kích hoạt với tham số `actionId` khớp với **Action ID** trong builder. Bạn có thể tự tạo ID theo ý muốn, chẳng hạn như "allowNotifications".
:::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ị 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-to-react-native-sdk-v4
---
---
title: "Migrate Adapty React Native SDK sang v. 4.0"
description: "Migrate sang Adapty React Native SDK v4.0 (beta) bằng cách thay thế các API paywall bằng API flow, tương thích với cả Flow Builder và Paywall Builder."
---
Adapty React Native SDK 4.0 (beta) giới thiệu flows và đổi tên các API paywall tương ứng. 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 nào trên Adapty Dashboard.
## Tham chiếu nhanh \{#quick-reference\}
| v3 | v4 |
|---|---|
| `adapty.getPaywall(placementId, locale?, params?)` | `adapty.getFlow(placementId, params?)` |
| `adapty.getPaywallForDefaultAudience(placementId, locale?, params?)` | `adapty.getFlowForDefaultAudience(placementId, params?)` |
| `adapty.getPaywallProducts(paywall)` | `adapty.getPaywallProducts(flow)` |
| `adapty.logShowPaywall(paywall)` | `adapty.logShowFlow(flow)` |
| `AdaptyPaywall` (kiểu) | `AdaptyFlow` |
| `createPaywallView(paywall)` | `createFlowView(flow)` |
| `AdaptyPaywallView` (component) | `AdaptyFlowView` |
| `EventHandlers` (kiểu) | `FlowEventHandlers` |
| `onPaywallShown` | `onAppeared` |
| `onPaywallClosed` | `onDisappeared` |
| `onRenderingFailed` | `onError` |
`AdaptyPaywallProduct` giữ nguyên tên — các sản phẩm vẫn thuộc về một flow, và `getPaywallProducts` giờ nhận một `AdaptyFlow`. Các phương thức `getFlow` và `getFlowForDefaultAudience` không còn nhận tham số `locale` nữa. Các phương thức view `present`, `dismiss`, `setEventHandlers`, và `showDialog`, cùng các event handler `onCloseButtonPress`, `onUrlPress`, `onCustomAction`, `onProductSelected`, `onPurchaseStarted`, `onPurchaseCompleted`, `onPurchaseFailed`, `onRestoreStarted`, `onRestoreCompleted`, `onRestoreFailed`, `onLoadingProductsFailed`, `onWebPaymentNavigationFinished`, và `onAndroidSystemBack` giữ nguyên tên như trong v3. Một số hành vi mặc định đã thay đổi — xem [Thay đổi hành vi mặc định](#default-behavior-changes).
## Phiên bản iOS tối thiểu \{#minimum-ios-version\}
Adapty React Native SDK 4.0 nâng phiên bản iOS deployment target tối thiểu từ iOS 13.0 lên **iOS 15.0**. Hãy đặt iOS deployment target của bạn thành 15.0 hoặc cao hơn trước khi nâng cấp.
## Cài đặt \{#installation\}
### Cập nhật gói
v4.0 là bản phát hành thử nghiệm (pre-release), vì vậy hãy chỉ định chính xác phiên bản — npm không tự động chọn các phiên bản pre-release qua cú pháp caret/tilde:
```bash showLineNumbers
npm install react-native-adapty@4.0.0
# or
yarn add react-native-adapty@4.0.0
```
### iOS: Các SDK native hiện được phân phối qua Swift Package Manager \{#ios-native-sdks-now-come-through-swift-package-manager\}
[Repo spec của CocoaPods sẽ chuyển sang chế độ chỉ đọc vào tháng 12 năm 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/), vì vậy bắt đầu từ v4, các SDK native `Adapty`, `AdaptyUI` và `AdaptyPlugin` **không còn được kéo vào dưới dạng sub-dependency của CocoaPods nữa** — podspec sẽ kéo chúng qua **Swift Package Manager** (thông qua helper `spm_dependency`). Điều này yêu cầu hai thay đổi sau:
- **React Native 0.75 trở lên** — cần thiết cho helper `spm_dependency` trong podspec. Nếu dùng phiên bản cũ hơn, `pod install` sẽ báo lỗi rõ ràng; hãy nâng cấp React Native trước, hoặc tiếp tục dùng `react-native-adapty` 3.x.
- **Dynamic frameworks** — các dependency SPM yêu cầu dynamic linkage. Cách bật tính năng này khác nhau giữa Expo và bare React Native.
#### Expo
Thêm config plugin [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/) và đặt iOS frameworks thành dynamic trong `app.json` (hoặc `app.config.js`):
```json showLineNumbers title="app.json"
{
"expo": {
"plugins": [
[
"expo-build-properties",
{
"ios": {
"useFrameworks": "dynamic"
}
}
]
]
}
}
```
Sau đó cài plugin và tạo lại native project:
```bash showLineNumbers
npx expo install expo-build-properties
npx expo prebuild --clean
```
#### Bare React Native
Thêm dynamic frameworks vào iOS target, sau đó cài lại pods:
```ruby showLineNumbers title="ios/Podfile"
use_frameworks! :linkage => :dynamic
```
```bash showLineNumbers
cd ios && pod install --repo-update
```
Nếu trước đây bạn đã kéo `Adapty`, `AdaptyUI`, hoặc `AdaptyPlugin` dưới dạng sub-dependencies của CocoaPods, hãy xóa các dòng `pod 'Adapty'`, `pod 'AdaptyUI'`, hoặc `pod 'AdaptyPlugin'` khỏi `Podfile` trước.
:::warning
Chuyển từ liên kết tĩnh mặc định sang dynamic framework có thể xung đột với các thư viện chưa hỗ trợ modular headers, và không tương thích với Flipper. Nếu gặp lỗi build, xem bài viết này về [tích hợp Swift Package Manager với thư viện React Native](https://www.callstack.com/blog/integrating-swift-package-manager-with-react-native-libraries).
:::
Xem [Cài đặt Adapty SDK](sdk-installation-reactnative) để biết hướng dẫn thiết lập đầy đủ.
## Lấy flows \{#fetching-flows\}
### getPaywall → getFlow
Kiểu trả về thay đổi từ `AdaptyPaywall` sang `AdaptyFlow`, và tham số `locale` bị xóa — khi render một flow, locale sẽ được tự động xác định; với các paywall tùy chỉnh, tất cả locale được trả về trong `flow.remoteConfigs`:
```diff showLineNumbers
- const paywall = await adapty.getPaywall('YOUR_PLACEMENT_ID', 'en');
+ const flow = await adapty.getFlow('YOUR_PLACEMENT_ID');
```
`getPaywallForDefaultAudience` được đổi tên theo cách tương tự:
```diff showLineNumbers
- const paywall = await adapty.getPaywallForDefaultAudience('YOUR_PLACEMENT_ID', 'en');
+ const flow = await adapty.getFlowForDefaultAudience('YOUR_PLACEMENT_ID');
```
### getPaywallProducts(paywall) → getPaywallProducts(flow)
`getPaywallProducts` giữ nguyên tên nhưng giờ nhận một `AdaptyFlow`:
```diff showLineNumbers
- const products = await adapty.getPaywallProducts(paywall);
+ const products = await adapty.getPaywallProducts(flow);
```
## Mô hình dữ liệu \{#data-model\}
`getFlow` trả về `AdaptyFlow` thay vì `AdaptyPaywall`, và cấu trúc đối tượng đã thay đổi:
| Trường `AdaptyPaywall` v3 | Trường `AdaptyFlow` v4 | Hành động |
|---|---|---|
| `remoteConfig?` (đơn lẻ) | `remoteConfigs?: AdaptyRemoteConfig[]` (mảng) | Một flow chứa một remote config cho mỗi ngôn ngữ đã cấu hình. Đọc cái phù hợp với người dùng: `flow.remoteConfigs?.find((c) => c.lang === 'en')`. |
| `products` | `flow.paywalls[i].productIdentifiers` | Các định danh sản phẩm hiện nằm trên từng biến thể flow, không còn trên flow nữa. |
| `webPurchaseUrl?` | `flow.paywalls[i].webPurchaseUrl` | Chuyển từ flow sang từng biến thể paywall. |
| `version?: number` | `flowVersionId?: string` | Đổi tên, và kiểu dữ liệu thay đổi từ `number` sang `string`. |
| `hasViewConfiguration` | đã xóa | Xóa mọi kiểm tra `hasViewConfiguration` khỏi code của bạn. |
| `requestLocale` | đã xóa | Locale không còn là một phần của model nữa. |
| _(mới)_ | `paywalls: AdaptyFlowPaywall[]` | Mỗi mục là một biến thể paywall trong flow. |
| _(mới)_ | `responseCreatedAt: number` | Timestamp phản hồi từ server, tính bằng mili giây. |
Product identifiers moved from the flow to each variation:
```diff showLineNumbers
- const ids = paywall.products;
+ const ids = flow.paywalls[0].productIdentifiers;
```
## Các phương thức Web paywall \{#web-paywall-methods\}
`openWebPaywall` và `createWebPaywallUrl` vẫn giữ nguyên tên, nhưng tham số đầu tiên bây giờ là `AdaptyFlowPaywall` (một biến thể flow) thay vì `AdaptyPaywall`. Bạn vẫn có thể truyền vào `AdaptyPaywallProduct`.
```diff showLineNumbers
const flow = await adapty.getFlow('YOUR_PLACEMENT_ID');
- await adapty.openWebPaywall(paywall);
+ await adapty.openWebPaywall(flow.paywalls[0]);
```
## Theo dõi lượt xem flow \{#tracking-flow-views\}
### logShowPaywall → logShowFlow
`logShowPaywall` được đổi tên thành `logShowFlow` và giờ nhận vào một `AdaptyFlow`. Sự kiện vẫn được ghi lại theo cùng một biến thể, nên 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
- await adapty.logShowPaywall(paywall);
+ await adapty.logShowFlow(flow);
```
Giống như ở v3, bạn không cần gọi phương thức này khi hiển thị các flow hoặc paywall được dựng 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 đó.
## Hiển thị flow \{#displaying-flows\}
### createPaywallView → createFlowView
Đổi tên hàm factory và truyền vào `AdaptyFlow`. Các phương thức của controller được trả về (`present`, `dismiss`, `setEventHandlers`, `showDialog`) không thay đổi:
```diff showLineNumbers
- import { createPaywallView } from 'react-native-adapty';
+ import { createFlowView } from 'react-native-adapty';
- const view = await createPaywallView(paywall);
+ const view = await createFlowView(flow);
await view.present();
```
### AdaptyPaywallView → AdaptyFlowView
Nếu bạn render bằng React component, hãy đổi tên component và truyền prop `flow`:
```diff showLineNumbers
- import { AdaptyPaywallView } from 'react-native-adapty';
+ import { AdaptyFlowView } from 'react-native-adapty';
- ```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).