├── 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 |
137 | );
138 | } else {
139 | return (
140 |
141 | Fetching products please wait...
142 |
143 | );
144 | }
145 | }
146 |
147 | const styles = StyleSheet.create({
148 | container: {
149 | flex: 1,
150 | backgroundColor: "white",
151 | alignItems: "center",
152 | justifyContent: "center",
153 | },
154 | title: {
155 | fontSize: 22,
156 | color: "red",
157 | },
158 | });
159 |
--------------------------------------------------------------------------------