xtablo-source/xtablo-expo/lib/purchases.ts
2026-05-03 09:28:46 +02:00

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;