122 lines
3.1 KiB
TypeScript
122 lines
3.1 KiB
TypeScript
import { Platform } from "react-native";
|
|
import Purchases, {
|
|
LOG_LEVEL,
|
|
PURCHASES_ERROR_CODE,
|
|
type CustomerInfo,
|
|
type PurchasesError,
|
|
type PurchasesOffering,
|
|
type PurchasesPackage,
|
|
} from "react-native-purchases";
|
|
|
|
const revenueCatAppleApiKey = process.env.EXPO_PUBLIC_REVENUECAT_APPLE_API_KEY?.trim() ?? "";
|
|
|
|
let configuredAppUserId: string | null = null;
|
|
|
|
export type BillingPackagePlan = "solo" | "annual";
|
|
|
|
export type BillingPackageOption = {
|
|
description: string | null;
|
|
id: string;
|
|
package: PurchasesPackage;
|
|
plan: BillingPackagePlan;
|
|
price: string;
|
|
title: string;
|
|
};
|
|
|
|
const isIosPurchasePlatform = () => Platform.OS === "ios";
|
|
|
|
const inferBillingPackagePlan = (input: string): BillingPackagePlan | null => {
|
|
const normalized = input.toLowerCase();
|
|
|
|
if (normalized.includes("annual") || normalized.includes("founder")) {
|
|
return "annual";
|
|
}
|
|
|
|
if (normalized.includes("solo")) {
|
|
return "solo";
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const canUseInAppPurchases = () => isIosPurchasePlatform() && revenueCatAppleApiKey.length > 0;
|
|
|
|
export const ensurePurchasesConfigured = async (appUserID: string) => {
|
|
if (!canUseInAppPurchases()) {
|
|
return false;
|
|
}
|
|
|
|
if (__DEV__) {
|
|
await Purchases.setLogLevel(LOG_LEVEL.DEBUG);
|
|
}
|
|
|
|
const isConfigured = await Purchases.isConfigured();
|
|
|
|
if (!isConfigured) {
|
|
Purchases.configure({
|
|
apiKey: revenueCatAppleApiKey,
|
|
appUserID,
|
|
});
|
|
configuredAppUserId = appUserID;
|
|
return true;
|
|
}
|
|
|
|
if (configuredAppUserId !== appUserID) {
|
|
await Purchases.logIn(appUserID);
|
|
configuredAppUserId = appUserID;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
const toBillingPackageOption = (pkg: PurchasesPackage): BillingPackageOption | null => {
|
|
const plan =
|
|
inferBillingPackagePlan(pkg.product.identifier) ?? inferBillingPackagePlan(pkg.identifier);
|
|
|
|
if (!plan) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
description: pkg.product.description ?? null,
|
|
id: pkg.product.identifier,
|
|
package: pkg,
|
|
plan,
|
|
price: pkg.product.priceString,
|
|
title: pkg.product.title,
|
|
};
|
|
};
|
|
|
|
export const getCurrentOffering = async (appUserID: string): Promise<PurchasesOffering | null> => {
|
|
const isConfigured = await ensurePurchasesConfigured(appUserID);
|
|
if (!isConfigured) {
|
|
return null;
|
|
}
|
|
|
|
const offerings = await Purchases.getOfferings();
|
|
return offerings.current;
|
|
};
|
|
|
|
export const getBillingPackageOptions = async (appUserID: string) => {
|
|
const offering = await getCurrentOffering(appUserID);
|
|
if (!offering) {
|
|
return [];
|
|
}
|
|
|
|
return offering.availablePackages
|
|
.map(toBillingPackageOption)
|
|
.filter((pkg): pkg is BillingPackageOption => Boolean(pkg));
|
|
};
|
|
|
|
export const purchaseBillingPackage = async (appUserID: string, pkg: PurchasesPackage) => {
|
|
await ensurePurchasesConfigured(appUserID);
|
|
return Purchases.purchasePackage(pkg);
|
|
};
|
|
|
|
export const restoreBillingPurchases = async (appUserID: string): Promise<CustomerInfo> => {
|
|
await ensurePurchasesConfigured(appUserID);
|
|
return Purchases.restorePurchases();
|
|
};
|
|
|
|
export const isPurchaseCancelledError = (error: unknown) =>
|
|
(error as PurchasesError | undefined)?.code === PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR;
|