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

Zakupy w aplikacji React Native: prosta implementacja. Samouczek

Ivan Dorofeev

Updated: 23 maja, 2023

23 min read

Content

62fdf143e0d4c00ce1483eba jp android tutorial 1 configuration 10

Międzyplatformowe frameworki tworzenia aplikacji z pewnością ułatwiają życie programistom, umożliwiając im tworzenie aplikacji na wiele platform jednocześnie. Są jednak też pewne ich wady. Na przykład React Native nie posiada gotowego narzędzia do wdrażania zakupów w aplikacji. Dlatego nieuchronnie będziesz musiał zwrócić się do bibliotek innych firm. 

Jakie istnieją opcje wdrożenia zakupów w aplikacji

Popularne bibliotek dla subskrypcji aplikacji (in-app subscriptions) w aplikacjach React Native to react-native-iap i expo-in-app-purchases. Będę jednak opowiadać o react-native-adapty, ponieważ istnieje sporo korzyści korzystania właśnie z niej, w porównaniu do innych bibliotek:

  • W przeciwieństwie do ich zapewnia walidację zakupu na serwerze (purchase validation).
  • Obsługuje wszystkie funkcje ostatnio zaimplementowane przez sklepy z aplikacjami,  od ofert promocyjnych (promo offers) aż po funkcje płatności z góry. Szybko zaczyna też obsługiwać wszelkie nowo pojawiające się funkcje.
  • Kod staje się bardziej przejrzysty i prosty.
  • Możesz modyfikować swoją ofertę produktową i dodawać lub usuwać nowe oferty bez konieczności przechodzenia przez pełny cykl wydawniczy. Nie ma potrzeby wydawania wersji beta i oczekiwania na zatwierdzenie.

Ale Adapty SDK to o wiele więcej. Otrzymujesz wbudowane narzędzia analityczne dla wszystkich kluczowych wskaźników, analizy kohortowej (cohort analysis), walidacji zakupów na serwerze (purchase validation), testów AB dla paywall (AB testing), kampanii promocyjnych z elastyczną segmentacją, integracji narzędzi analitycznych innych firm i wiele więcej.

W tym artykule

Na razie porozmawiajmy o konfigurowaniu zakupów w aplikacji w aplikacjach natywnych React. O czym dziś porozmawiamy:

  1. Dlaczego Expo nie będzie działać w przypadku zakupów wewnątrz aplikacji w aplikacjach React Native.
  2. Tworzenie konta dewelopera.
  3. Konfigurowanie Adapty:
    Konfigurowanie App Store
    Konfigurowanie Play Store
  4. Dodawanie subskrypcji.
  5. Tworzenie paywalla.
  6. Instalacja react-native-adapty.
  7. Przykładowa aplikacja i wynik. 

W tym przewodniku postaramy się stworzyć aplikację, która wyświetli zdjęcia kota subskrybowanym użytkownikom i zaproponuje wszystkim innym ofertę subskrypcji. 

Dlaczego Expo nie będzie działać w przypadku zakupów wewnątrz aplikacji w aplikacjach React Native

Krótko mówiąc: Expo „zarządzane” nie obsługuje natywnych metod przetwarzania zakupów w sklepach z aplikacjami (znanych również jako zestawy sklepowe). Musisz albo trzymać się czystego RN, albo używać prostego przepływu Expo.

Od razu muszę rozczarować tych, którzy myśleli o korzystaniu z Expo: to nie zadziała. Expo to framework React Native, który znacznie ułatwia tworzenie aplikacji. Ich zarządzany przepływ pracy nie jest jednak zgodny z przetwarzaniem zakupów/subskrypcji. Expo nie używa żadnego natywnego kodu w swoich metodach i komponentach (są bazowane na JS), co jest wymagane dla zestawów sklepowych. Nie ma sposobu na wdrożenie zakupów w aplikacji w sklepach mobilnych z JavaScript, więc będziesz musiał „wysuwać”.

Tworzenie konta dewelopera

Najpierw musisz skonfigurować konta w app store, a także utworzyć i skonfigurować zakupy oraz subskrypcje dla systemów iOS i Android. To nie powinno zająć więcej niż 20 minut.

Jeśli nadal nie skonfigurowałeś konta dewelopera i produktów w App Store Connect i/lub konsoli Google Play, zapoznaj się z tymi poradnikami:

  • Dla iOS: przeczytaj poradnik od początku, aż do nagłówka „Uzyskiwanie listy SKProduct”, ponieważ tam zaczynamy omawiać natywne implementacje. 
  • Dla Androida: przeczytaj poradnik od początku do nagłówka „Pobieranie listy produktów w aplikacji”.

Konfigurowanie Adapty

W przypadku react-native-adapty musisz najpierw skonfigurować pulpit nawigacyjny Adapty. To nie zajmie dużo czasu, a zapewni Ci wszystkie zalety wymienione powyżej, które posiada Adapty w porównaniu do twardego kodowania.

W trzecim kroku pojawi się przypomnienie o konfiguracji App Store i Google Play.

61dd2e3df0eb779b1966c336 wwqbyclqbg22r4uxyue6f wty 8kx1lgeepf9 ctrv5mcvrihoe1upbhw 0jth wqwkhjc4az

W przypadku systemu iOS musisz:

  • Określić identyfikator pakietu (Bundle ID);
  • Skonfigurować powiadomienia serwera App Store (App Store Server Notifications);
  • Określić shared secret App Store Connect. 

Te pola są wymagane, aby zakupy działały. 

Każde pole zawiera podpowiedzi „Przeczytaj, jak”, które zawierają instrukcje krok po kroku. Sprawdź je, jeśli masz jakieś pytania.

Bundle ID to unikalny identyfikator Twojej aplikacji. Musi pasować do tego, który podałeś w Xcode, w Targets > [App Name] > General:

61dd2e3d0bcefe3f92ed87c6 ofteg 29 hasv9sywd5gpm4vxtbwppkdlkqkatwvl3jprjkq3ffp3xzdcuett5qf uzavsew

W przypadku systemu Android wymagane pola to nazwa pakietu (Package Name) i plik klucza konta usługi (Service Account Key File). Wszystkie te pola mają swoje własne wskazówki, które również warto przeczytać. Nazwa pakietu w systemie Android działa tak, jak identyfikator pakietu w systemie iOS. Musi pasować do tego podanego w kodzie, który można znaleźć w pliku /android/app/build.gradle w android.defaultConfig.applicationId:

61dd2e3dad4d57621f800483 qbh6fhwgcmjiqsriilrm2ubc2xjswr8buchwntern5xn8fkxqxgep4hbrftvqyfu3rgx4dg x3c6iyvemvxarmvemiu

W czwartym kroku pojawi się żądanie podłączenia zestawu Adapty SDK do aplikacji. Pomiń na razie ten krok — wrócimy do tego nieco później.

Po zarejestrowaniu się sprawdź zakładkę ustawienia i pamiętaj, że tutaj można znaleźć publiczny klucz SDK. Będziesz potrzebować tego klucza później.

61dd2e3d3fce0994fd3f0c38 0 z1xu4tx6yoskp5gysva4jz ksmpt59lwmlpt8mol5fmyuqn1gygyvmqf52rgz5drhlvtp2gekrd0pwygz8pydmejdeskwggxp5 f6kevd ftdziotbnzpcv6bsqgojjk9unzt

Dodawanie subskrypcji

Adapty wykorzystuje produkty dla różnych subskrypcji. Twoja subskrypcja zdjęć kotów może być tygodniowa, półroczna lub roczna. Każda z tych opcji będzie osobnym produktem Adapty.

Określmy na pulpicie nawigacyjnym, że mamy jeden produkt. Aby to zrobić, przejdź do Products & A/B Tests → Products i kliknij Create product. 

W tym miejscu musisz określić nazwę produktu, czyli sposób, w jaki ta subskrypcja będzie wyglądać na pulpicie nawigacyjnym Adapty. 

Musisz również określić App Store Product ID i Play Store Product ID. Jeśli chcesz, określ okres i nazwę dla analityki. Zapisz zmiany (Save).

61dd2e3d3abe166794791c09 fukrvgjrhor1rtmrrmvq4mar f3rdr2nqpbojb44aa5ipwcq 9bbq7ajpq085sroobeltpnw9rtdfah a0pqoqthyqbj8ayaahikjioylj1gszmlfh mhwlqowynrpaabjjkvoln

Tworzenie paywalla

Teraz musisz zaprojektować paywall, który jest ekranem, ograniczającym dostęp Użytkownika do funkcji premium i wyświetlającym mu ofertę subskrypcji. Musisz dodać utworzony produkt do paywalla. Aby to zrobić, kliknij „Create paywall” w tej samej sekcji (Products & A/B Tests → Paywalls).

  • Wybierz taką nazwę Paywall, którą ty i twój zespół będziecie mogli łatwo skojarzyć z produktem na podstawie samej nazwy paywalla.
  • Identyfikator paywalla (Paywall ID) będzie używany do wyświetlania tego paywalla w aplikacji. Dla naszej przykładowej aplikacji użyjemy „cats_paywall.”
  • Z listy rozwijanej Product wybierz subskrypcję.

Kliknij zapisz i opublikuj.

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

To wszystko, jeśli chodzi o konfigurację. Teraz będziemy dodawać zależności i pisać kod.

Instalacja react-native-adapty

1. Najpierw dodaj zależność:

yarn add react-native-adapty

2. Zainstaluj iOS pods. Jeśli nie masz jeszcze CLI pod, zdecydowanie polecam go pobrać. Na pewno będzie to bardzo potrzebne przy pracy z iOS.

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

3. Ponieważ projekty React Native iOS są pisane w Obj-C, musisz utworzyć Swift Bridging Header, aby Obj-C mógł czytać biblioteki Swift. Aby to zrobić, po prostu otwórz projekt Xcode i utwórz nowy plik Swift. Xcode zapyta, czy chcesz utworzyć nagłówek pomostowy (bridging header), który jest dokładnie tym, czego potrzebujesz. Kliknij utwórz (Create).

61dd2e3da26af882142a7518 o2nycy3aeotxdr5m bcojaezt3hnj uzz 1jim7k25r9bn2jedf yhyvhgwx2qlequduyul0hwxxpjixeoe
61dd2e3ef99ba01c6f90aa08 rldljwkg131iukgbpxmwbjwu5q0m mhqxje3s9i1hljldxeau b8ehfimaw2ttxqq

4. Dla Androida, upewnij się, że projekt – domyślnie /android/build.gradle używa wtyczki kotlin-gradle-plugin w wersji 1.4.0 lub nowszej:

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

5. W przypadku Androida musisz włączyć multiDex, który można znaleźć w pliku konfiguracyjnym aplikacji (domyślnie /android/app/build.gradle.)

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

Voila, wszystko gotowe, możesz odpocząć chwilę przed rozpoczęciem kodowania!

Pobieranie listy produktów w aplikacji

Jest mnóstwo przydatnych rzeczy dziejących się pod kuratelą react-native-adapty. Z pewnością będziesz ich potrzebował, prędzej czy później, dlatego powinieneś zainicjować bibliotekę na samym początku swojego działania. Idź tak wysoko, jak to tylko możliwe w kodzie aplikacji (możesz to zrobić bezpośrednio w aplikacji.tsx) i rozpocznij inicjalizację:

// 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' });
  },[]);
  ...
}

Tutaj zastąp MY_PUBLIC_KEY publicznym kluczem SDK znalezionym w ustawieniach pulpitu nawigacyjnego. W rzeczywistości metoda activateAdapty() może być wywoływana więcej niż raz i w więcej niż jednym miejscu, ale pozostaniemy przy tej wersji.

Teraz możemy pobrać produkty, które dodaliśmy w panelu Adapty:

import { adapty } from 'react-native-adapty';

async function getProducts() {
	const {paywalls, products} = await adapty.paywalls.getPaywalls();

	return products;
}

Teraz przejdźmy do praktyki: spróbujemy zbudować małą aplikację, w której będziemy mogli przeglądać produkty z naszych paywall i dokonywać zakupów.

Przykładowa aplikacja

Od teraz będę się streszczać, żeby nie skomplikować niepotrzebnie logiki. Będę również kodować w TypeScript, aby pokazać, które typy są używane i gdzie. Do testowania użyję mojego starego, dobrego iPhone’a 8. Pamiętaj, że od iOS 14 App Store zabrania używania zestawów sklepowych w emulatorach— możesz testować tylko przy użyciu urządzeń fizycznych.

Komponent główny App.tsx

1. Najpierw stwórzmy komponent główny App.tsx, który będzie posiadał przycisk wyświetlania paywall. Skonfigurowaliśmy już nawigację przez react-native-navigation– uważamy, że jest to znacznie lepsze, niż opcja nawigacji react zalecana w oficjalnych dokumentach.

ISSUE

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" },
});

O co tu chodzi? Podczas montowania wywoływana jest funkcja fetchPaywalls (). Aktywuje SDK i zapisuje paywall w tym stanie, aby użytkownik nie musiał czekać na pobieranie po naciśnięciu przycisku. Widoczny jest tylko jeden przycisk, który ma przenieść użytkownika do paywalla, który wcześniej zaprojektowaliśmy na pulpicie nawigacyjnym.

Właściwie jest możliwe dostarczenie paywalli tutaj, bez zapisywania ich w stanie. Domyślnie adapty.paywalls.getPaywalls() pobierze je z pamięci podręcznej (po cachowaniu ich podczas uruchomienia), co oznacza, że nie będziesz musiał czekać na metodę, aby skomunikować się z serwerem.

Oto wynik:

react native in-app purchases tutorial payall

Komponent paywall

2. Zapiszmy komponent paywall w tym samym pliku.

// 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"),
  },
});

Tutaj po prostu zmapujemy produkty z paywall i wyświetlimy przycisk zakupu obok każdego produktu.

Rejestracja ekranu

3. Aby zobaczyć, jak to wygląda, zarejestrujmy ten ekran w react-native-navigation. Jeśli używasz innej nawigacji, pomiń ten krok. Mój plik główny indeks.js wygląda tak:

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" } }] } },
  });
});

Przycisk „Wyświetl paywall” („Display the paywall”)

4. Teraz musimy tylko przypisać akcję do przycisku „Wyświetl paywall”. W naszym przypadku poprosi o modal poprzez nawigację.

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);
        },
      },
    },
  });

Cały plik 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

O to chodziło! Teraz możesz wyświetlać te paywalle swoim użytkownikom.

Jeśli chcesz przetestować subskrypcję na iOS w środowisku sandbox, musisz utworzyć własne konto testera sandbox. Pamiętaj, że subskrypcje sandbox są szybko unieważniane,aby ułatwić testowanie. W przypadku Androida nie potrzebujesz żadnych dodatkowych kont – możesz nawet uruchamiać testy w emulatorze.

Subscribe to Adapty newsletter

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

Sprawdzanie, czy użytkownik ma aktywne subskrypcje

Nadal musimy zdecydować, gdzie przechowywać aktywne dane subskrypcji, aby przyznać użytkownikowi końcowemu dostęp do jego treści premium. Adapty pomoże nam również w tym , ponieważ zapisuje wszystkie zakupy, związane z użytkownikiem. Zróbmy to w ten sposób: jeśli użytkownik nie ma subskrypcji, zostanie wyświetlony przycisk paywall. Jeśli ją posiada, pokażemy mu zdjęcie kota. 

Ponieważ aktywne dane subskrypcji są pobierane z serwera lub pamięci podręcznej, potrzebny jest program ładujący. Dla uproszczenia dodajmy stany isLoading i 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();
  }, []);

  // ...
}
// ...

Oto co się zmieniło: dodaliśmy twa oznaczenia do stanu. Cała zawartość fetchPaywalls() jest teraz otoczona blokadą try-catch, aby kod osiągnął setIsLoading (false) w każdym możliwym scenariuszu. Aby sprawdzić, czy użytkownik ma aktywną subskrypcję, pobieramy profil użytkownika (który zawiera wszystkie dane jego subskrypcji) i widzimy wartość profil.accessLevels.premium.isActive. Możesz użyć tylu poziomów dostępu (accessLevels), które są w zasadzie tylko stopniami subskrypcji, takimi jak Gold lub Premium, ilu potrzebujesz, ale na razie zachowajmy domyślną wartość. Adapty automatycznie utworzy poziom dostępu premium, a dla większości aplikacji to wystarczy. isActive pozostanie true, chociaż istnieje aktywna subskrypcja z tym poziomem dostępu

Od tego momentu wszystko wygląda całkiem prosto. Jeśli użytkownik ma status subskrypcji stopnia premium, nie ma potrzeby pobierania paywalla — wystarczy wyłączyć program ładujący i wyświetlić zawartość. 

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>;
};

Tutaj dodajemy funkcję, która renderuje zawartość, a także pewną logikę do onRequestBuy: mianowicie aktualizowanie stanu isPremium i zamykanie modalu.

Tak wygląda efekt końcowy:

Cały plik:

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"),
  },
});

Podsumujmy wszystko

Stworzyliśmy całkiem nieźle wyglądającą i niezwykle przydatną aplikację subskrypcyjną. Osoby, które zapłacą, zobaczą koty, a wszystkim innym wyświetli się paywall. Ten przewodnik powinien nauczyć cię wszystkiego, czego możesz potrzebować, aby zaimplementować zakupy w aplikacji. A dla osób, które nie mogą się doczekać zagłębiania się w zestawy sklepowe – czekajcie na kolejne artykuły. Dzięki!