Achats intégrés sous React native : implémentation simple. Tutoriel
Updated: mai 23, 2023
25 min read
Les cadres de développement d’applications multi-plateformes facilitent certainement la vie des développeurs, en leur permettant de créer des applications pour plusieurs plateformes à la fois. Il y a cependant quelques inconvénients. Par exemple, React Native ne dispose d’aucun outil prêt à l’emploi pour la mise en œuvre des achats intégrés (in-app purchases). Par conséquent, vous devrez inévitablement vous tourner vers des bibliothèques tierces.
Quelles sont les options disponibles pour la mise en œuvre des achats intégrés ?
Les bibliothèques les plus populaires pour les abonnements (subscriptions) intégrés dans les applications React Native sont react-native-iap et expo-in-app-purchases. Je vais cependant parler de react-native-adapty, car elle présente un certain nombre d’avantages par rapport aux autres bibliothèques :
- Contrairement à ces derniers, il assure la validation des achats (purchase validation) sur le serveur.
- Il prend en charge toutes les fonctionnalités récemment implémentées par les magasins d’applications, depuis les offres promotionnelles (promo offers) jusqu’aux fonctionnalités de paiement anticipé (pay-upfront). Il est également rapide pour prendre en charge les nouvelles fonctionnalités à venir.
- Le code finit par être plus clair et plus simple.
- Vous pouvez modifier votre offre de produits et ajouter ou supprimer de nouvelles offres sans devoir passer par le cycle complet de publication. Il n’est pas nécessaire de publier des versions bêta et d’attendre l’approbation.
Il y a bien plus dans le SDK Adapty que ça. Vous disposez d’outils d’analyse intégrés pour tous les indicateurs clés, d’une analyse de cohorte (cohort analysis), d’une validation des achats sur serveur, d’un test AB pour les paywalls, de campagnes promotionnelles avec une segmentation flexible, d’intégrations d’outils d’analyse tiers, etc.
Dans cet article
Pour l’instant, nous allons parler du paramétrage des achats intégrés dans les applications React Native. Voici ce que nous allons couvrir aujourd’hui :
- Pourquoi Expo ne fonctionne pas pour les achats intégrés dans les applications React Native.
- Création d’un compte de développeur.
- Configuration de Adapty:
Configuration de l’App Store
Configuration de Play Store - Ajout d’abonnements.
- Création d’un paywall.
- Installation de react-native-adapty.
- Un exemple d’application et le résultat.
Dans ce guide, nous allons essayer de créer une application qui affiche des photos de chats pour les utilisateurs abonnés et propose une offre d’abonnement à tous les autres.
Pourquoi Expo ne fonctionne pas pour les achats intégrés dans les applications React Native ?
Pour résumer brièvement la situation : Expo « managed” ne prend pas en charge les méthodes natives des magasins d’applications pour le traitement des achats (également connues sous le nom de kits de magasin). Vous devrez soit vous en tenir au RN pur, soit utiliser le flux de travail de Expo.
D’emblée, je dois décevoir ceux qui ont pensé à utiliser Expo : ça ne marchera pas. Expo est un cadre React Native qui facilite grandement le développement des applications. Leur flux de travail géré n’est cependant pas compatible avec le traitement des achats et des abonnements. Expo n’utilise pas de code natif dans ses méthodes et ses composants (les deux sont en JS uniquement), ce qui est requis pour les kits de magasin. Il n’y a aucun moyen d’implémenter les achats intégrés dans les boutiques mobiles avec JavaScript, vous devrez donc vous « éjecter ».
Création d’un compte de développeur
Tout d’abord, vous devrez configurer des comptes app store, ainsi que créer et configurer les achats et les abonnements pour iOS et Android. Cela ne devrait pas vous prendre plus de 20 minutes.
Si vous n’avez toujours pas configuré votre compte de développeur et vos produits dans App Store Connect et/ou Google Play Console, consultez ces guides :
- Pour iOS: lisez le guide depuis le début et jusqu’à la rubrique « Obtenir la liste de SKProduct »(Getting the list of SKProduct), car c’est là que nous commençons à parler des implémentations natives.
- Pour Android: lisez le guide depuis le début et jusqu’à la rubrique « Obtenir une liste de produits dans une application ».
Configurer Adapty
Pour react-native-adapty, vous devrez d’abord configurer votre tableau de bord Adapty. Cela ne prendra pas beaucoup de temps, mais vous obtiendrez tous les avantages énumérés ci-dessus qu’Adapty a sur le codage en dur.
À la troisième étape, vous serez invité à configurer l’App Store et Google Play.
Pour iOS, vous aurez besoin de :
- Spécifiez l’ID du Bundle ;
- Paramétrez les notifications du serveur (server notifications) App Store ;
- Spécifiez le secret partagé (shared secret) App Store Connect.
Ces champs sont obligatoires pour que les achats fonctionnent.
Chaque champ dispose d’une indication « Découvrez comment »(Read how) qui contient des guides pratiques étape par étape. Consultez-les si vous avez des questions.
L’ID du Bundle est l’ID unique de votre application. Il doit correspondre à celui que vous avez spécifié dans Xcode, dans Targets > [App Name] > General :
Pour Android, les champs obligatoires sont le nom du paquet et le fichier clé du compte de service. Tous ces domaines ont également leurs propres conseils de lecture. Le nom du paquet fait sous Android ce que l’ID du Bundle fait sous iOS. Il doit correspondre à celui que vous avez spécifié dans votre code, qui se trouve dans le fichier /android/app/build.gradle dans android.defaultConfig.applicationId :
A la quatrième étape, vous serez invité à connecter SDK Adapty à votre application. Mais sautez cette étape pour l’instant, nous y reviendrons un peu plus tard.
Une fois que vous vous êtes inscrit, consultez l’onglet des paramètres et n’oubliez pas que c’est là que se trouve votre clé publique SDK. Vous aurez besoin de la clé plus tard.
Ajout d’un abonnement
Adapty utilise des produits pour différents abonnements. Votre abonnement aux photos de chats peut être hebdomadaire, semestriel ou annuel. Chacune de ces options sera un produit Adapty distinct.
Spécifions dans le tableau de bord que nous avons un seul produit. Pour ce faire, allez dans Products & A/B Tests → Products et cliquez sur Create product.
Ici, vous devrez spécifier le nom du produit, c’est-à-dire comment cet abonnement apparaîtra dans votre tableau de bord Adapty.
Vous devrez également indiquer l’ID du produit App Store et l’ID du produit Play Store. Si vous le souhaitez, spécifiez également la période et le nom pour l’analyse. Cliquez sur Save.
Création d’un paywall
Vous devez maintenant concevoir un paywall, c’est-à-dire un écran qui limite l’accès de l’utilisateur aux fonctions premium et lui propose une offre d’abonnement. Vous devrez ajouter le produit que vous avez créé à votre paywall. Pour ce faire, cliquez sur Create paywall dans la même section (Products & A/B Tests → Paywalls).
- Choisissez un nom de paywall tel que vous et votre équipe pourrez facilement déduire, simplement en regardant le nom, de quel paywall il s’agit.
- Vous utiliserez l’ID du paywall pour afficher ce paywall dans votre application. Pour notre exemple d’application, nous utiliserons « cats_paywall ».
- Dans le menu déroulant » Product « , sélectionnez votre abonnement.
Cliquez sur Save & publish.
C’est tout pour la configuration. Maintenant, nous allons ajouter les dépendances et écrire le code.
Installation de react-native-adapty
1. Tout d’abord, ajoutez la dépendance :
yarn add react-native-adapty
2. Installez les pods iOS. Si vous n’avez pas encore le pod CLI, je vous recommande vivement de le télécharger. Vous en aurez certainement beaucoup besoin dans le développement iOS.
#pods get installed into the native iOS project, which, by default, is the /ios folderpod install --project-directory=ios
3. Les projets iOS React Native étant écrits en Obj-C, vous devrez créer un en-tête de pontage Swift pour qu’Obj-C puisse lire les bibliothèques Swift. Pour ce faire, il suffit d’ouvrir votre projet Xcode et de créer un nouveau fichier Swift. Xcode vous demandera si vous voulez créer un en-tête de pontage, ce qui est exactement ce que vous voulez. Cliquez sur Create.
4. Pour Android, assurez-vous que le projet-/android/build.gradle par défaut- utilise le kotlin-gradle-plugin de la version 1.4.0 ou supérieure :
... buildscript {
... dependencies {
... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0"
}
} ...
5. Pour Android, vous devez activer multiDex, qui se trouve dans le fichier de configuration de l’application (/android/app/build.gradle par défaut).
...
android {
... defaultConfig {
... multiDexEnabled true
}
}
Voilà, vous êtes prêt et vous pouvez v̶o̶u̶s̶ ̶r̶e̶p̶o̶s̶e̶r̶ commencer à coder !
Récupération de la liste des produits dans l’application
Il y a des tonnes de choses utiles qui se passent sous le capot de react-native-adapty. Vous en aurez certainement besoin, tôt ou tard, c’est pourquoi vous devez initialiser la bibliothèque au tout début de votre flux. Allez aussi loin que possible dans le code de votre application (vous pouvez aussi le faire directement dans le App.tsx) et lancez l’initialisation :
// 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' });
},[]);
...
}
Ici, remplacez MY_PUBLIC_KEY par votre clé publique SDK qui se trouve dans les paramètres du tableau de bord. En fait, la méthode activateAdapty() peut être invoquée plus d’une fois et à plus d’un endroit, mais nous nous en tiendrons à cette conception.
Maintenant, nous pouvons récupérer les produits que nous avons ajoutés dans le tableau de bord Adapty :
import { adapty } from 'react-native-adapty';
async function getProducts() {
const {paywalls, products} = await adapty.paywalls.getPaywalls();
return products;
}
Maintenant, passons à la pratique : Nous essaierons de créer une petite application où l’on pourra parcourir les produits de nos paywalls et faire des achats.
Exemple d’application
Je vais faire court à partir de maintenant pour éviter de rendre la logique de base trop compliquée. Je coderai également en TypeScript pour vous montrer quels types sont utilisés et où. Pour les tests, je vais utiliser mon bon vieil iPhone 8. N’oubliez pas qu’à partir d’iOS 14, l’App Store interdit l’utilisation de kits de magasin dans des émulateurs : vous ne pouvez tester qu’à l’aide d’appareils physiques.
Composant racine App.tsx
1. Tout d’abord, créons un composant racine App.tsx qui comportera un bouton d’affichage du paywall. Nous avons déjà configuré la navigation via react-native-navigation nous pensons qu’elle est bien meilleure que l’option react-navigation recommandée dans la documentation officielle.
PROBLÈME
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" },
});
Que se passe-t-il ici ? Lors du montage, la fonction fetchPaywalls() est invoquée. Il active le SDK et enregistre les paywalls en l’état afin que l’utilisateur n’ait pas à attendre pour les récupérer après avoir appuyé sur le bouton. Il n’y a qu’un seul bouton dans la vue qui est censé amener l’utilisateur au paywall que nous avons précédemment conçu dans le tableau de bord.
En fait, il est possible de récupérer les paywalls ici même, sans les sauvegarder en l’état. Par défaut, adapty.paywalls.getPaywalls() ira les chercher dans le stockage du cache (après les avoir mis en cache au lancement), ce qui signifie que vous n’aurez pas à attendre que la méthode communique avec le serveur.
Voici le résultat :
Un composant paywall
2. Écrivons un composant paywall dans le même fichier.
// 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"),
},
});
Ici, nous allons simplement mapper les produits du paywall et afficher un bouton d’achat à côté de chaque produit.
Enregistrement de l’écran
3. Pour voir à quoi cela ressemble, enregistrons cet écran dans react-native-navigation. Si vous utilisez un autre système de navigation, passez cette étape. Mon fichier racine index.js ressemble à ceci :
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" } }] } },
});
});
Bouton « Afficher le paywall ».
4. Maintenant, il ne nous reste plus qu’à attribuer une action au bouton « Afficher le paywall ». Dans notre cas, il s’agit d’une modale via 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);
},
},
},
});
L’intégralité du fichier 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"),
},
});
C’est ça ! Maintenant, vous pouvez afficher ces paywalls à vos utilisateurs.
Si vous souhaitez tester votre abonnement iOS dans un sandbox, vous devrez créer votre propre compte de testeur sandbox. N’oubliez pas que les abonnements sandbox sont rapidement invalidés pour faciliter les tests. Pour Android, vous n’avez pas besoin de comptes supplémentaires et vous pouvez même effectuer des tests dans un émulateur.
Vérifier si l’utilisateur a des abonnements actifs
Nous devons encore décider où stocker les données d’abonnement actif pour permettre à l’utilisateur final d’accéder à son contenu premium. Adapty nous aidera également dans ce domaine, car il enregistre tous les achats associés à l’utilisateur. Faisons comme suit : si l’utilisateur n’a pas d’abonnement, il sera invité à cliquer sur un bouton « paywall ». S’ils le font, on leur montrera une photo de chat.
Étant donné que les données de l’abonnement actif sont extraites soit du serveur, soit du stockage en cache, vous aurez besoin d’un chargeur. Par souci de simplicité, ajoutons les états isLoading et 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();
}, []);
// ...
}
// ...
Voici ce qui a changé : nous avons ajouté deux drapeaux à l’état. L’ensemble du contenu de fetchPaywalls() est maintenant enveloppé dans un bloc try-catch afin que le code atteigne setIsLoading(false) dans tous les scénarios possibles. Pour vérifier si l’utilisateur a un abonnement actif, nous récupérons le profil de l’utilisateur (qui contient toutes ses données d’abonnement) et voyons la valeur de profile.accessLevels.premium.isActive. Vous pouvez utiliser autant de niveaux d’accès (accessLevels) — qui sont essentiellement des niveaux d’abonnement, tels que Gold ou Premium—que vous le souhaitez, mais conservons la valeur par défaut pour le moment. Adapty créera automatiquement le niveau d’accès premium, et pour la plupart des applications, cela sera suffisant. isActive restera vrai tant qu’il y aura un abonnement actif avec ce niveau d’accès.
A partir de là, tout semble assez simple. Si l’utilisateur a le statut d’abonné premium, il n’est pas nécessaire de récupérer les paywalls : il suffit de désactiver le chargeur et d’afficher le contenu.
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>;
};
Ici, nous ajoutons une fonction qui rend le contenu ainsi qu’une certaine logique à onRequestBuy : à savoir, la mise à jour de l’état de isPremium et la fermeture de la modale.
Voilà le résultat final :
Le dossier entier :
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"),
},
});
En résumé
Nous avons fini par créer une application d’abonnement très jolie et extrêmement utile. Ceux qui paient verront des chats, et tous les autres auront des paywalls à la place. Ce guide devrait vous avoir appris tout ce dont vous pourriez avoir besoin pour mettre en place des achats intégrés dans votre application. Et pour ceux qui ont hâte d’approfondir les kits de magasin, restez à l’écoute pour en savoir plus. Merci !