From bb4cd1b0fc42c304b79aa6c7f1fa1f5090277cc1 Mon Sep 17 00:00:00 2001 From: AyrisAI Date: Thu, 20 Aug 2026 16:15:44 +0300 Subject: [PATCH] fix(mobile): stop matching entitlement by hardcoded identifier string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RevenueCat's entitlement identifier contains U+2024 (ONE DOT LEADER), not a plain period — "Menul.io Pro" never matched the real "Menul⁚io Pro" key, so isProActive() stayed false even after a real purchase (confirmed via a genuine sandbox trial that RevenueCat recorded as active while the app kept showing "Ücretsiz plan"). RevenueCat's API doesn't allow renaming an entitlement's lookup_key after creation (405), so fixed on our side instead: this app only ever grants one entitlement, so "any active entitlement" == Pro, no identifier string needed. --- apps/mobile/src/app/account/subscription.tsx | 4 ++-- apps/mobile/src/lib/purchases.ts | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/mobile/src/app/account/subscription.tsx b/apps/mobile/src/app/account/subscription.tsx index ff33ee5..88e3705 100644 --- a/apps/mobile/src/app/account/subscription.tsx +++ b/apps/mobile/src/app/account/subscription.tsx @@ -6,7 +6,7 @@ import { Ionicons } from "@expo/vector-icons"; import type { CustomerInfo, PurchasesOffering, PurchasesPackage } from "react-native-purchases"; import Purchases from "react-native-purchases"; import { getActiveRestaurant } from "@/lib/active-restaurant"; -import { configurePurchases, getCurrentOffering, getCustomerInfo, isProActive, PRO_ENTITLEMENT_ID } from "@/lib/purchases"; +import { configurePurchases, getActiveEntitlement, getCurrentOffering, getCustomerInfo, isProActive } from "@/lib/purchases"; export default function SubscriptionScreen() { const insets = useSafeAreaInsets(); @@ -74,7 +74,7 @@ export default function SubscriptionScreen() { } const isPro = customerInfo ? isProActive(customerInfo) : false; - const expiresAt = customerInfo?.entitlements.active[PRO_ENTITLEMENT_ID]?.expirationDate; + const expiresAt = customerInfo ? getActiveEntitlement(customerInfo)?.expirationDate : null; if (loading) { return ( diff --git a/apps/mobile/src/lib/purchases.ts b/apps/mobile/src/lib/purchases.ts index d596a35..94392b1 100644 --- a/apps/mobile/src/lib/purchases.ts +++ b/apps/mobile/src/lib/purchases.ts @@ -2,7 +2,6 @@ import { Platform } from "react-native"; import Purchases, { type CustomerInfo, type PurchasesOffering } from "react-native-purchases"; const API_KEY = process.env.EXPO_PUBLIC_REVENUECAT_API_KEY; -export const PRO_ENTITLEMENT_ID = "Menul.io Pro"; let configuredForRestaurantId: string | null = null; @@ -32,6 +31,16 @@ export async function getCustomerInfo(): Promise { return Purchases.getCustomerInfo(); } +// Checking by exact entitlement identifier is fragile — RevenueCat's +// dashboard let the identifier get created with a non-ASCII character +// ("Menul⁚io Pro" uses U+2024, not a plain period), which silently never +// matched a hardcoded string. This app only ever grants one entitlement, +// so "any active entitlement" is Pro and doesn't depend on that string. export function isProActive(info: CustomerInfo): boolean { - return info.entitlements.active[PRO_ENTITLEMENT_ID] !== undefined; + return Object.keys(info.entitlements.active).length > 0; +} + +export function getActiveEntitlement(info: CustomerInfo) { + const [first] = Object.values(info.entitlements.active); + return first ?? null; }