Report: State of in-app subscriptions in the US 2023 Get a report

Tính năng mua trong ứng dụng React native: cách triển khai đơn giản Hướng dẫn

Ivan Dorofeev

Updated: May 23, 2023

36 min read

Content

62fdf143e0d4c00ce1483eba jp android tutorial 1 configuration 10 7

Khung phát triển ứng dụng đa nền tảng chắc chắn giúp cuộc sống của các nhà phát triển trở nên dễ dàng hơn, cho phép họ cùng lúc xây dựng ứng dụng cho nhiều nền tảng. Dù vậy, khung này cũng có một số nhược điểm. Ví dụ như React Native không có sẵn công cụ để triển khai mua trong ứng dụng (in-app purchase). Do đó, chắc chắn bạn sẽ phải chuyển sang các thư viện của bên thứ ba. 

Tùy chọn có sẵn để triển khai mua trong ứng dụng

Các thư viện phổ biến cho gói đăng ký mua trong ứng dụng (in-app subscription) trong các ứng dụng React Native là react-native-iap và expo-in-app-purchases. Tuy nhiên, tôi sẽ nói về react-native-adapty vì thư viện này có một vài lợi ích so với các thư viện khác:

  • Khác với số còn lại, react-native-adapty cung cấp xác thực mua (purchase validation) dựa trên máy chủ.
  • Nó hỗ trợ tất cả các tính năng được triển khai gần đây bởi các cửa hàng ứng dụng, từ ưu đãi khuyến mãi (promo offer) đến tính năng thanh toán trước (pay upfront). Thư viện này cũng nhanh nhạy trong việc hỗ trợ các tính năng mới sắp ra mắt.
  • Mã trở nên rõ ràng và đơn giản hơn.
  • Bạn có thể sửa đổi ưu đãi sản phẩm của mình và thêm hoặc xóa các ưu đãi mới mà không cần phải thực hiện toàn bộ chu kỳ phát hành. Bạn không cần phát hành phiên bản beta và đợi chấp thuận nữa.

Với Adapty SDK, bạn có thể nhận được nhiều hơn thế Bạn nhận được các công cụ phân tích tích hợp cho tất cả các chỉ số chính, phân tích nhóm (cohort analysis), xác thực mua dựa trên máy chủ, thử nghiệm AB (AB testing) cho tường phí (paywall), chiến dịch khuyến mãi với phân khúc linh hoạt, tích hợp công cụ phân tích của bên thứ ba, v.v.

Nội dung bài viết

Bây giờ, hãy nói về thiết lập mua trong ứng dụng trong các ứng dụng React Native. Sau đây là nội dung chúng tôi sẽ trình bày hôm nay:

  1. Nguyên nhân Expo không hoạt động đối với mua trong ứng dụng trong các ứng dụng React Native.
  2. Tạo tài khoản nhà phát triển.
  3. Cấu hình Adapty:
    Cấu hình App Store
    Cấu hình Play Store
  4. Thêm gói đăng ký (subscription).
  5. Tạo tường phí.
  6. Cài đặt react-native-adapty.
  7. Ứng dụng mẫu và kết quả. 

Trong hướng dẫn này, chúng tôi sẽ cố gắng xây dựng một ứng dụng hiển thị hình ảnh mèo cho những người dùng đã đăng ký và nhắc nhở người dùng chưa đăng ký với thông tin ưu đãi gói đăng ký. 

Nguyên nhân Expo không hoạt động đối với mua trong ứng dụng trong các ứng dụng React Native

Nói ngắn gọn: Quy trình “được quản lý của Expo không hỗ trợ các phương pháp gốc (native) mà cửa hàng ứng dụng cung cấp để xử lý giao dịch mua (còn gọi là bộ dụng cụ cửa hàng). Bạn sẽ cần phải tuân theo RN thuần túy hoặc sử dụng quy trình thuần túy của Expo.

Ngay bây giờ, tôi sẽ phải làm thất vọng những người nghĩ đến việc sử dụng Expo: cách này sẽ không hiệu quả. Expo là một khung React Native giúp công việc phát triển ứng dụng trở nên dễ dàng hơn. Tuy nhiên, quy trình được quản lý của họ không tương thích với việc xử lý giao dịch mua/đăng ký. Expo không sử dụng bất kỳ mã gốc nào trong các phương thức và thành phần (cả hai đều là chỉ JS). Mã này có vai trò bắt buộc đối với bộ công cụ cửa hàng. Không có cách nào để triển khai mua trong ứng dụng trong các cửa hàng trên thiết bị di động bằng JavaScript, vì vậy bạn sẽ cần phải “đẩy”.

Tạo tài khoản nhà phát triển

Trước tiên, bạn cần phải thiết lập tài khoản cửa hàng ứng dụng, cũng như tạo và cấu hình các giao dịch mua và gói đăng ký cho cả iOS và Android. Việc này hầu như khôngt tiêu tốn quá 20 phút.

Nếu bạn vẫn chưa cấu hình tài khoản nhà phát triển và sản phẩm của mình trong App Store Connect và/hoặc Google Play Console, xem các hướng dẫn sau:

  • Đối với iOS: đọc hướng dẫn từ đầu đến tiêu đề “Lấy danh sách SKProduct”, vì đó là phần chúng ta bắt đầu thảo luận về các triển khai gốc. 
  • Đối với Android: đọc hướng dẫn từ đầu đến tiêu đề “Lấy danh sách sản phẩm trong một ứng dụng”.

Cấu hình Adapty

Đối với react-native-adapty, trước tiên bạn cần cấu hình bảng điều khiển Adapty của mình. Việc cấu hình tuy không mất nhiều thời gian nhưng sẽ giúp bạn có được tất cả những lợi thế nêu trên của Adapty so với mã hóa cứng.

Ở bước thứ ba, bạn sẽ được nhắc cấu hình App Store và Google Play.

61dd2e3df0eb779b1966c336 wwqbyclqbg22r4uxyue6f wty 8kx1lgeepf9 ctrv5mcvrihoe1upbhw 0jth wqwkhjc4az wnkjavifb eb9riv9vztvwn3rvqbfghyv0cjx8ytj7d5fc4of7zeu7ezdhvkxd 4

Đối với iOS, bạn cần:

  • Chỉ định ID gói;
  • Thiết lập Thông báo máy chủ (server notification) App Store;
  • Chỉ định khóa bí mật chia sẻ (shared secret) App Store Connect. 

Những trường này có vai trò bắt buộc để giao dịch mua hoạt động. 

Mỗi trường có một gợi ý ‘Read how’ chứa hướng dẫn thực hiện từng bước. Xem những hướng dẫn này nếu bạn có thắc mắc.

ID gói là ID duy nhất của ứng dụng. Thông tin này phải khớp với ID gói bạn đã chỉ định trong Xcode, tại Targets > [App Name] > General:

61dd2e3d0bcefe3f92ed87c6 ofteg 29 hasv9sywd5gpm4vxtbwppkdlkqkatwvl3jprjkq3ffp3xzdcuett5qf uzavsew j 9jeqswh hx7tugan03hol7qmnukdbzhefyla6ctghd8mwdxfb9bo6ynbhqatt 4

Đối với Android, các trường bắt buộc là Package Name và Service Account Key File. Tất cả những trường này cũng đều có gợi ý Read how riêng. Tên gói trong Android giống với ID gói trong iOS. Nó phải khớp với tên gói bạn đã chỉ định trong mã. Tên này có tại tệp /android/app/build.gradle trong android.defaultConfig.applicationId:

61dd2e3dad4d57621f800483 qbh6fhwgcmjiqsriilrm2ubc2xjswr8buchwntern5xn8fkxqxgep4hbrftvqyfu3rgx4dg x3c6iyvemvxarmvemiu awzpyhlntli64em6lnkwaqaxcsou4spkkyk2usia jyq 4

Tại bước thứ tư, bạn sẽ được nhắc kết nối Adapty SDK với ứng dụng. Tuy nhiên, hãy tạm bỏ qua bước này—chúng ta sẽ quay lại sau.

Sau khi bạn đã đăng ký, hãy xem tab cài đặt và nhớ rằng đây là nơi có khóa SDK công khai của bạn. Say này bạn sẽ cần đến khóa này.

61dd2e3d3fce0994fd3f0c38 0 z1xu4tx6yoskp5gysva4jz ksmpt59lwmlpt8mol5fmyuqn1gygyvmqf52rgz5drhlvtp2gekrd0pwygz8pydmejdeskwggxp5 f6kevd ftdziotbnzpcv6bsqgojjk9unzt 4

Thêm gói đăng ký

Adapty sử dụng sản phẩm cho các gói đăng ký khác nhau. Gói đăng ký hình ảnh mèo của bạn có thể là hàng tuần, hai năm hoặc hàng năm. Mỗi tùy chọn này sẽ là một sản phẩm Adapty riêng biệt.

Hãy cùng chỉ định trong bảng điều khiển rằng chúng ta có một sản phẩm. Để làm vậy, truy cập Products & A/B Tests → Products và nhấp vào Create product. 

Tại đây, bạn sẽ cần chỉ định tên sản phẩm, nghĩa là diện mạo gói đăng ký này trong bảng điều khiển Adapty của bạn. 

Bạn cũng cần chỉ định App Store Product ID và Play Store Product ID. Nếu muốn, hãy chỉ định cả khoảng thời gian và tên cho phân tích. Nhấp vào Save.

61dd2e3d3abe166794791c09 fukrvgjrhor1rtmrrmvq4mar f3rdr2nqpbojb44aa5ipwcq 9bbq7ajpq085sroobeltpnw9rtdfah a0pqoqthyqbj8ayaahikjioylj1gszmlfh mhwlqowynrpaabjjkvoln 4

Tạo tường phí

Bây giờ, bạn sẽ cần thiết kế tường phí, đây là một màn hình hạn chế quyền truy cập của người dùng vào các tính năng cao cấp và nhắc họ với một ưu đãi gói đăng ký. Bạn sẽ cần thêm sản phẩm bạn đã tạo vào tường phí. Để làm vậy, nhấp vào Create paywall trong cùng một phần (Products & A/B Tests → Paywalls).

  • Chọn tên Tường phí mà bạn và nhóm của bạn chỉ cần nhìn vào là có thể dễ dàng suy ra được rằng đó là tường phí nào.
  • Bạn sẽ sử dụng ID tường phí để hiển thị tường phí này trong ứng dụng. Đối với ứng dụng mẫu của chúng tôi, chúng tôi sẽ sử dụng “cats_paywall.”
  • Trong danh sách thả xuống Product, chọn gói đăng ký của bạn.

Nhấp vào Save & publish.

61dd2e3ddced150628dcbffb uw2xexcu4rprox7 esl56chynz2mu1iqtae1xlweot55m7lqpugfjxtzp kyg vtawjwjhcpkmuno ysa i el8xnv2v 6seczhpmdhy7xsswndb1 2kbuu9bfhqltew h4yq5tj 4

Trên đây là phần cấu hình. Bây giờ, chúng tôi sẽ thêm các phần phụ thuộc và viết mã.

Cài đặt react-native-adapty

1. Trước tiên, thêm phần phụ thuộc:

yarn add react-native-adapty

2. Cài đặt iOS pod. Nếu bạn chưa có CLI pod, tôi đặc biệt khuyên bạn nên tải xuống. Bạn chắc chắn sẽ cần đến nó rất nhiều trong quá trình phát triển iOS.

#pods get installed into the native iOS project, which, by default, is the /ios folderpod install --project-directory=ios

3. Vì các dự án iOS React Native được viết bằng Obj-C nên bạn sẽ cần tạo Swift Bridging Header để Obj-C có thể đọc được thư viện Swift. Để làm vậy, chỉ cần mở dự án Xcode và tạo một tệp Swift mới. Xcode sẽ hỏi bạn có muốn tạo tiêu đề bắc cầu – chính là điều bạn muốn. hay không. Nhấp vào Create.

61dd2e3da26af882142a7518 o2nycy3aeotxdr5m bcojaezt3hnj uzz 1jim7k25r9bn2jedf yhyvhgwx2qlequduyul0hwxxpjixeoe 8uyyqv2kwmydhcag9znmrignaptegex1ztzxa2nvppzyiqwwga0 4
61dd2e3ef99ba01c6f90aa08 rldljwkg131iukgbpxmwbjwu5q0m mhqxje3s9i1hljldxeau b8ehfimaw2ttxqq o3ztd2zrgzdwmjuqhr5pxcnokw0 9iblxynoakgsv1fcu8ci1hai5ia5bx31bisokijcvt 4

4. Đối với Android, hãy đảm bảo rằng dự án—/android/build.gradle theo mặc định—đang sử dụng phiên bản kotlin-gradle-plugin 1.4.0 trở lên:

... buildscript {    ... dependencies {      ... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0"    }  } ...

5. Đối với Android, bạn sẽ cần bật multiDex. MultiDex có trong tệp cấu hình của ứng dụng (/android/app/build.gradle theo mặc định.)

...  android {    ... defaultConfig {      ... multiDexEnabled true    }  }

Vậy là xong, bạn đã sẵn sàng và có thể bắt đầu viết mã!

Truy xuất danh sách sản phẩm trong ứng dụng

Có rất nhiều điều hữu ích diễn ra trong react-native-adapty. Bạn chắc chắn sẽ cần những điều này, dù sớm hay muộn, đó là lý do tại sao bạn nên khởi chạy thư viện ngay từ đầu quy trình của mình. Kéo lên hết mức có thể trong mã ứng dụng (bạn cũng có thể làm việc này ngay trong App.tsx) và bắt đầu khởi chạy:

// import the method import { activateAdapty } from 'react-native-adapty';  // We’ve had this App component in our app’s root const App: React.FC = () => {   ... // we’re invoking it once in a root component on mount   useEffect(() => {     activateAdapty({ sdkKey: 'MY_PUBLIC_KEY' });   },[]);   ... }

Tại đây, thay thế MY_PUBLIC_KEY bằng khóa SDK công khai có trong cài đặt bảng điều khiển của bạn. Trên thực tế, phương thức activeAdapty() có thể được gọi nhiều lần và ở nhiều nơi, nhưng chúng ta sẽ chỉ nói về thiết kế này.

Bây giờ, ta có thể truy xuất các sản phẩm đã được thêm vào bảng điều khiển Adapty:

import { adapty } from 'react-native-adapty';  async function getProducts() { 	const {paywalls, products} = await adapty.paywalls.getPaywalls();  	return products; }

Bây giờ, hãy cùng bắt đầu thực hành: Chúng tôi sẽ cố gắng xây dựng một ứng dụng nhỏ cho phép duyệt các sản phẩm từ tường phí và thực hiện giao dịch mua.

Ứng dụng mẫu

Từ phần này tôi sẽ trình bày ngắn gọn để tránh làm cho logic cơ sở trở nên quá phức tạp. Tôi cũng sẽ lập trình trong TypeScript để cho bạn thấy loại được sử dụng và nơi sử dụng. Để thử nghiệm, tôi sẽ sử dụng chiếc iPhone 8 cũ mà vẫn tốt của mình. Hãy nhớ rằng từ iOS 14 trở đi, App Store cấm sử dụng bộ công cụ lưu trữ trong trình giả lập—bạn chỉ có thể thử nghiệm bằng thiết bị vật lý.

Thành phần gốc (root component) App.tsx

1. Đầu tiên, hãy tạo một thành phần gốc App.tsx có nút hiển thị tường phí (paywall display button). Chúng tôi đã cấu hình điều hướng qua react-native-navigation—chúng tôi tin rằng tùy chọn này tốt hơn nhiều so với tùy chọn react-navigation được đề xuất trong các tài liệu chính thức.

VẤN ĐỀ

import React, { useEffect, useState } from "react"; import { Button, StyleSheet, View } from "react-native"; import { adapty, activateAdapty, AdaptyPaywall } from "react-native-adapty";  export const App: React.FC = () => {   const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);    useEffect(() => {     async function fetchPaywalls(): Promise<void> {       await activateAdapty({ sdkKey: "MY_PUBLIC_KEY" });        const result = await adapty.paywalls.getPaywalls();       setPaywalls(result.paywalls);     }      fetchPaywalls();   }, []);    return (     <View style={styles.container}>       <Button         title="Show the paywall"         onPress={() => {           const paywall = paywalls.find(             (paywall) => paywall.developerId === "cats_paywall"           );            if (!paywall) {             return alert("There is no such paywall");           } 					// Switching to a paywall...         }}       />     </View>   ); };  const styles = StyleSheet.create({   container: { flex: 1, alignItems: "center", justifyContent: "center" }, });

Chuyện gì đang xảy ra ở đây? Trong mount, hàm fetchPaywalls() được gọi. Hàm này kích hoạt SDK và lưu tường phí ở trạng thái để người dùng không phải đợi nạp sau khi nhấn vào nút. Chỉ có một nút trong chế độ xem được cho là sẽ đưa người dùng đến tường phí mà chúng ta đã thiết kế trước đây trong bảng điều khiển.

Trên thực tế, có thể nạp tường phí ngay tại đây mà không cần lưu vào trạng thái. Theo mặc định, adapty.paywalls.getPaywalls() sẽ nạp tường phí từ bộ nhớ đệm (cache storage) (sau khi bộ nhớ đệm khi khởi chạy), có nghĩa là bạn sẽ không phải đợi phương thức giao tiếp với máy chủ.

Đây là kết quả:

react native in-app purchases tutorial payall

Thành phần tường phí (paywall component)

2. Hãy viết một thành phần tường phí trong cùng tệp.

// there are more imports here import React, { useEffect, useState } from "react"; import {   Button,   SafeAreaView,   StyleSheet,   Text,   View,   PlatformColor, } from "react-native"; import {   adapty,   activateAdapty,   AdaptyPaywall,   AdaptyProduct, } from "react-native-adapty"; import { Navigation } from "react-native-navigation";  // ...  interface PaywallProps {   paywall: AdaptyPaywall;   onRequestBuy: (product: AdaptyProduct) => void | Promise<void>; } export const Paywall: React.FC<PaywallProps> = ({ paywall, onRequestBuy }) => {   const [isLoading, setIsLoading] = useState<boolean>(false);    return (     <SafeAreaView style={styles.container}>       {paywall.products.map((product) => (         <View key={product.vendorProductId}>           <Text>{product.localizedTitle}</Text>           <Button             title={`Buy for за ${product.localizedPrice}`}             disabled={isLoading}             onPress={async () => {               try {                 setIsLoading(true);                 await onRequestBuy(product);               } catch (error) {                 alert("Error occured :(");               } finally {                 setIsLoading(false);               }             }}           />         </View>       ))}     </SafeAreaView>   ); };  // A new key const styles = StyleSheet.create({   container: { flex: 1, alignItems: "center", justifyContent: "center" },   paywallContainer: {     flex: 1,     alignItems: "center",     justifyContent: "space-evenly",     backgroundColor: PlatformColor("secondarySystemBackground"),   }, });

Tại đây, chúng ta sẽ chỉ sắp xếp các sản phẩm từ tường phí và hiển thị nút mua (purchase button) bên cạnh mỗi sản phẩm.

Đăng ký màn hình (Registering the screen)

3. Để xem quá trình này diễn ra như thế nào, hãy đăng ký màn hình này trong react-native-navigation. Nếu bạn đang sử dụng một số điều hướng khác, hãy bỏ qua bước này. Tệp gốc index.js của tôi trông như thế này:

import "react-native-gesture-handler"; import { Navigation } from "react-native-navigation";  import { App, Paywall } from "./App";  Navigation.registerComponent("Home", () => App); Navigation.registerComponent("Paywall", () => Paywall);  Navigation.events().registerAppLaunchedListener(() => {   Navigation.setRoot({     root: { stack: { children: [{ component: { name: "Home" } }] } },   }); });

Nút “Display the paywall”

4. Bây giờ, chúng ta sẽ phải chỉ định một hành động cho nút “Display the paywall”. Trong trường hợp, nút sẽ yêu cầu một modal thông qua Navigation.

Navigation.showModal<PaywallProps>({     component: {       name: "Paywall",       passProps: {         paywall,         onRequestBuy: async (product) => {           const purchase = await adapty.purchases.makePurchase(product);           // Doing everything we need           console.log("purchase", purchase);         },       },     },   });

Toàn bộ tệp App.tsx:

import React, { useEffect, useState } from "react"; import {   Button,   SafeAreaView,   StyleSheet,   Text,   View,   PlatformColor, } from "react-native"; import {   adapty,   activateAdapty,   AdaptyPaywall,   AdaptyProduct, } from "react-native-adapty"; import { Navigation } from "react-native-navigation"; export const App: React.FC = () => {   const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);   useEffect(() => {     async function fetchPaywalls(): Promise<void> {       await activateAdapty({         sdkKey: "MY_PUBLIC_KEY",       });        const result = await adapty.paywalls.getPaywalls();       setPaywalls(result.paywalls);     }      fetchPaywalls();   }, []);    return (     <View style={styles.container}>       <Button         title="Show paywall"         onPress={() => {           const paywall = paywalls.find(             (paywall) => paywall.developerId === "cats_paywall"           );            if (!paywall) {             return alert("There is no such paywall");           }            Navigation.showModal<PaywallProps>({             component: {               name: "Paywall",               passProps: {                 paywall,                 onRequestBuy: async (product) => {                   const purchase = await adapty.purchases.makePurchase(product);                   // Doing everything we need                   console.log("purchase", purchase);                 },               },             },           });         }}       />     </View>   ); };  interface PaywallProps {   paywall: AdaptyPaywall;   onRequestBuy: (product: AdaptyProduct) => void | Promise<void>; } export const Paywall: React.FC<PaywallProps> = ({ paywall, onRequestBuy }) => {   const [isLoading, setIsLoading] = useState<boolean>(false);    return (     <SafeAreaView style={styles.paywallContainer}>       {paywall.products.map((product) => (         <View key={product.vendorProductId}>           <Text>{product.localizedTitle}</Text>           <Button             title={`Buy for ${product.localizedPrice}`}             disabled={isLoading}             onPress={async () => {               try {                 setIsLoading(true);                 await onRequestBuy(product);               } catch (error) {                 alert("Error occured :(");               } finally {                 setIsLoading(false);               }             }}           />         </View>       ))}     </SafeAreaView>   ); };  const styles = StyleSheet.create({   container: { flex: 1, alignItems: "center", justifyContent: "center" },   paywallContainer: {     flex: 1,     alignItems: "center",     justifyContent: "space-evenly",     backgroundColor: PlatformColor("secondarySystemBackground"),   }, });

6203b7f5d4192527a3d05ac8 react native tutorial 2 cropped 4

Vậy là xong! Bây giờ, bạn có thể hiển thị tường phí này với người dùng.

Nếu bạn muốn thử nghiệm gói đăng ký iOS trong một sandbox thì bạn sẽ cần tạo tài khoản thử nghiệm sandbox (sandbox tester account) của riêng bạn. Hãy ghi nhớ rằng gói đăng ký sandbox (sandbox subscription) nhanh chóng bị vô hiệu để việc thử nghiệm dễ dàng hơn. Đối với Android, bạn không cần tài khoản phụ nào—bạn có thể chạy thử nghiệm trong một trình giả lập.

Subscribe to Adapty newsletter

Get fresh paywall ideas, subscription insights, and mobile app news every month!

Kiểm tra xem người dùng có gói đăng ký đang hoạt động (active subscription) nào không

Chúng ta vẫn cần quyết định nơi lưu trữ dữ liệu gói đăng ký đang hoạt động để cấp cho người dùng cuối quyền truy cập vào nội dung cao cấp (premium content). Adapty cũng sẽ giúp chúng ta xử lý vấn đề này vì Adapty lưu tất cả giao dịch mua được liên kết với người dùng. Hãy làm theo cách này: nếu người dùng không có gói đăng ký thì nút tường phí sẽ nhắc họ. Nếu họ đăng ký thì chúng ta sẽ cho họ xem ảnh chú mèo. 

Vì dữ liệu gói đăng ký đang hoạt động được truy xuất từ máy chủ hoặc bộ nhớ đệm, bạn sẽ cần một trình tải. Để đơn giản hơn, hãy thêm trạng thái isLoading và isPremium.

// ... export const App: React.FC = () => { 	const [isLoading, setIsLoading] = useState<boolean>(true); 	const [isPremium, setIsPremium] = useState<boolean>(false);   const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);  	useEffect(() => {     async function fetchPaywalls(): Promise<void> {       try {         await activateAdapty({           sdkKey: "MY_PUBLIC_KEY",         });          const profile = await adapty.purchases.getInfo();         const isSubscribed = profile.accessLevels.premium.isActive;         setIsPremium(isSubscribed);          if (!isSubscribed) {           const result = await adapty.paywalls.getPaywalls();           setPaywalls(result.paywalls);         }       } finally {         setIsLoading(false);       }     }      fetchPaywalls();   }, []);    // ... } // ...

Đây là những gì đã thay đổi: chúng tôi đã thêm hai lá cờ vào trạng thái. Toàn bộ nội dung của fetchPaywalls() hiện được gói trong khối try-catch để mã đến được setIsLoading (false) trong bất kỳ trường hợp nào có thể xảy ra. Để kiểm tra xem người dùng có gói đăng ký đang hoạt động hay không, chúng tôi sẽ truy xuất hồ sơ của người dùng (chứa tất cả dữ liệu gói đăng ký của họ) và xem giá trịs profile.accessLevels.premium.isActive. Bạn có thể sử dụng bao nhiêu cấp truy cập (accessLevels) tùy ý—về cơ bản chỉ là các cấp của gói đăng ký (subscription tiers), chẳng hạn như Gold hoặc Premium—nhưng bây giờ hãy giữ giá trị mặc định. Adapty sẽ tự động tạo cấp truy cập cao cấp, và thế là đủ với hầu hết các ứng dụng. isActive sẽ giữ giá trị true trong khi có một gói đăng ký có cấp truy cập này

Từ đây trở đi, mọi thứ trở nên khá đơn giản. Nếu người dùng có trạng thái gói đăng ký cao cấp thì không cần nạp tường phí—chỉ cần vô hiệu hóa trình tải và hiển thị nội dung. 

export const App: React.FC = () => { // ... const renderContent = (): React.ReactNode => {   if (isLoading) {     return <Text>Loading...</Text>;   }    if (isPremium) {     return (       <Image         source={{           url: "https://25.media.tumblr.com/tumblr_lugj06ZSgX1r4xjo2o1_500.gif",           width: Dimensions.get("window").width * 0.8,           height: Dimensions.get("window").height * 0.8,         }}       />     );   }    return (     <Button       title="Show paywall"       onPress={() => {         const paywall = paywalls.find(           (paywall) => paywall.developerId === "cats_paywall"         );          if (!paywall) {           return alert("There is no such paywall");         }          Navigation.showModal<PaywallProps>({           component: {             name: "Paywall",             passProps: { 	            paywall, 		          onRequestBuy: async (product) => { 		            const purchase = await adapty.purchases.makePurchase(product); 	              const isSubscribed = 		              purchase.purchaserInfo.accessLevels?.premium.isActive;                 setIsPremium(isSubscribed);                 Navigation.dismissAllModals();               },             },           },         });       }}     />   ); };  return <View style={styles.container}>{renderContent()}</View>; };

Ở đây, chúng tôi đang thêm một hàm hiển thị nội dung và logic cho onRequestBuy: cụ thể là cập nhật trạng thái của isPremium và đóng modal.

Đây là kết quả cuối cùng:

Toàn bộ tệp:

import React, { useEffect, useState } from "react"; import {   Button,   SafeAreaView,   StyleSheet,   Text,   View,   PlatformColor,   Image,   Dimensions, } from "react-native"; import {   adapty,   activateAdapty,   AdaptyPaywall,   AdaptyProduct, } from "react-native-adapty"; import { Navigation } from "react-native-navigation";  export const App: React.FC = () => {   const [isLoading, setIsLoading] = useState<boolean>(true);   const [isPremium, setIsPremium] = useState<boolean>(false);   const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);    useEffect(() => {     async function fetchPaywalls(): Promise<void> {       try {         await activateAdapty({           sdkKey: "MY_PUBLIC_KEY",         });          const profile = await adapty.purchases.getInfo();         const isSubscribed = profile.accessLevels.premium.isActive;         setIsPremium(isSubscribed);          if (!isSubscribed) {           const result = await adapty.paywalls.getPaywalls();           setPaywalls(result.paywalls);         }       } finally {         setIsLoading(false);       }     }      fetchPaywalls();   }, []);    const renderContent = (): React.ReactNode => {     if (isLoading) {       return <Text>Loading...</Text>;     }      if (isPremium) {       return (         <Image           source={{             uri: "https://25.media.tumblr.com/tumblr_lugj06ZSgX1r4xjo2o1_500.gif",             width: Dimensions.get("window").width * 0.8,             height: Dimensions.get("window").height * 0.8,           }}         />       );     }      return (       <Button         title="Show a paywall"         onPress={() => {           const paywall = paywalls.find(             (paywall) => paywall.developerId === "cats_paywall"           );            if (!paywall) {             return alert("There is no such a paywall");           }            Navigation.showModal<PaywallProps>({             component: {               name: "Paywall",               passProps: {                 paywall,                 onRequestBuy: async (product) => {                   const purchase = await adapty.purchases.makePurchase(product);                   const isSubscribed =                     purchase.purchaserInfo.accessLevels?.premium.isActive;                   setIsPremium(isSubscribed);                   Navigation.dismissAllModals();                 },               },             },           });         }}       />     );   };    return <View style={styles.container}>{renderContent()}</View>; }; interface PaywallProps {   paywall: AdaptyPaywall;   onRequestBuy: (product: AdaptyProduct) => void | Promise<void>; } export const Paywall: React.FC<PaywallProps> = ({ paywall, onRequestBuy }) => {   const [isLoading, setIsLoading] = useState<boolean>(false);    return (     <SafeAreaView style={styles.paywallContainer}>       {paywall.products.map((product) => (         <View key={product.vendorProductId}>           <Text>{product.localizedTitle}</Text>           <Button             title={`Buy for ${product.localizedPrice}`}             disabled={isLoading}             onPress={async () => {               try {                 setIsLoading(true);                 await onRequestBuy(product);               } catch (error) {                 alert("An error occured :(");               } finally {                 setIsLoading(false);               }             }}           />         </View>       ))}     </SafeAreaView>   ); };  const styles = StyleSheet.create({   container: { flex: 1, alignItems: "center", justifyContent: "center" },   paywallContainer: {     flex: 1,     alignItems: "center",     justifyContent: "space-evenly",     backgroundColor: PlatformColor("secondarySystemBackground"),   }, });

Tóm lại

Chúng ta đã hoàn thành dựng một ứng dụng sử dụng gói đăng ký (subscription app) đẹp mắt và cực kỳ hữu ích. Người dùng trả phí sẽ thấy mèo, còn người khác sẽ nhận được tường phí. Hướng dẫn này đã chỉ dẫn bạn mọi thứ có thể cần để triển khai mua trong ứng dụng vào ứng dụng của chính bạn. Với những ai mong muốn tìm hiểu sâu hơn về bộ công cụ lưu trữ (store kits)—hãy theo dõi để biết thêm. Xin cảm ơn!