├── backend-code.js └── App.js /backend-code.js: -------------------------------------------------------------------------------- 1 | const functions = require("firebase-functions"); 2 | const { google } = require("googleapis"); 3 | 4 | exports.validate = functions 5 | .region("europe-west2") 6 | .https.onCall(async (data) => { 7 | functions.logger.info(data, { structuredData: true }); 8 | const auth = new google.auth.GoogleAuth({ 9 | keyFile: "insert-your-keyfile-here... or use a secret manager :)", 10 | scopes: ["https://www.googleapis.com/auth/androidpublisher"], 11 | }); 12 | functions.logger.info(JSON.parse(data)["purchaseToken"], { 13 | structuredData: true, 14 | }); 15 | 16 | try { 17 | const res = await google 18 | .androidpublisher("v3") 19 | .purchases.subscriptions.get({ 20 | packageName: "insert-your-package-name-here", 21 | subscriptionId: JSON.parse(data)["productId"], 22 | token: JSON.parse(data)["purchaseToken"], 23 | auth: auth, 24 | }); 25 | functions.logger.info(res, { 26 | structuredData: true, 27 | }); 28 | if (res.status == 200) { 29 | functions.logger.info(res.data.paymentState === 1, { 30 | structuredData: true, 31 | }); 32 | return { isActiveSubscription: res.data.paymentState === 1 }; 33 | } 34 | return { error: -1 }; 35 | } catch (error) { 36 | functions.logger.error(error, { 37 | structuredData: true, 38 | }); 39 | return { error: -1 }; 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { 3 | StyleSheet, 4 | Text, 5 | Alert, 6 | Button, 7 | Platform, 8 | View, 9 | Image, 10 | } from "react-native"; 11 | 12 | import IAP from "react-native-iap"; 13 | 14 | // Platform select will allow you to use a different array of product ids based on the platform 15 | const items = Platform.select({ 16 | ios: [], 17 | android: ["rniapt_699_1m"], 18 | }); 19 | 20 | let purchaseUpdateSubscription; 21 | let purchaseErrorSubscription; 22 | let img = require("./walter.jpg"); 23 | 24 | export default function App() { 25 | const [purchased, setPurchased] = useState(false); //set to true if the user has active subscription 26 | const [products, setProducts] = useState({}); //used to store list of products 27 | 28 | const validate = async (receipt) => { 29 | try { 30 | // send receipt to backend 31 | const deliveryReceipt = await fetch("add your backend link here", { 32 | headers: { "Content-Type": "application/json" }, 33 | method: "POST", 34 | body: JSON.stringify({ data: receipt }), 35 | }).then((res) => { 36 | res.json().then((r) => { 37 | // do different things based on response 38 | if (r.result.error == -1) { 39 | Alert.alert("Error", "There has been an error with your purchase"); 40 | } else if (r.result.isActiveSubscription) { 41 | setPurchased(true); 42 | } else { 43 | Alert.alert("Expired", "your subscription has expired"); 44 | } 45 | }); 46 | }); 47 | } catch (error) { 48 | Alert.alert("Error!", error.message); 49 | } 50 | }; 51 | 52 | useEffect(() => { 53 | IAP.initConnection() 54 | .catch(() => { 55 | console.log("error connecting to store..."); 56 | }) 57 | .then(() => { 58 | IAP.getSubscriptions(items) 59 | .catch(() => { 60 | console.log("error finding items"); 61 | }) 62 | .then((res) => { 63 | setProducts(res); 64 | }); 65 | 66 | IAP.getPurchaseHistory() 67 | .catch(() => {}) 68 | .then((res) => { 69 | try { 70 | const receipt = res[res.length - 1].transactionReceipt; 71 | if (receipt) { 72 | validate(receipt); 73 | } 74 | } catch (error) {} 75 | }); 76 | }); 77 | 78 | purchaseErrorSubscription = IAP.purchaseErrorListener((error) => { 79 | if (!(error["responseCode"] === "2")) { 80 | Alert.alert( 81 | "Error", 82 | "There has been an error with your purchase, error code" + 83 | error["code"] 84 | ); 85 | } 86 | }); 87 | purchaseUpdateSubscription = IAP.purchaseUpdatedListener((purchase) => { 88 | const receipt = purchase.transactionReceipt; 89 | if (receipt) { 90 | validate(receipt); 91 | IAP.finishTransaction(purchase, false); 92 | } 93 | }); 94 | 95 | return () => { 96 | try { 97 | purchaseUpdateSubscription.remove(); 98 | } catch (error) {} 99 | try { 100 | purchaseErrorSubscription.remove(); 101 | } catch (error) {} 102 | try { 103 | IAP.endConnection(); 104 | } catch (error) {} 105 | }; 106 | }, []); 107 | 108 | if (purchased) { 109 | return ( 110 | 111 | WELCOME TO THE APP! 112 | 113 | 114 | ); 115 | } 116 | 117 | if (products.length > 0) { 118 | return ( 119 | 120 | Welcome to my app! 121 | 122 | This app requires a subscription to use, a purchase of the 123 | subscription grants you access to the entire app 124 | 125 | 126 | {products.map((p) => ( 127 |