package com.example.casera.utils import com.example.casera.data.DataManager /** * Canonical product IDs for in-app subscriptions. * * These must match the product IDs configured in: * - Apple App Store Connect (StoreKit configuration) * - Google Play Console (subscription products) * * All code referencing subscription product IDs should use these constants * instead of hardcoded strings to ensure consistency. */ object SubscriptionProducts { const val MONTHLY = "com.example.casera.pro.monthly" const val ANNUAL = "com.example.casera.pro.annual" /** All product IDs as a list, useful for querying store product details. */ val all: List = listOf(MONTHLY, ANNUAL) } /** * Helper for checking subscription limits and determining when to show upgrade prompts. * * Reads ALL subscription state from DataManager (single source of truth). * The current tier is derived from the backend subscription status: * - If expiresAt is present and non-empty, user is "pro" * - Otherwise, user is "free" * * RULES: * 1. Backend limitations OFF: Never show upgrade view, allow everything * 2. Backend limitations ON + limit=0: Show upgrade view, block access entirely (no add button) * 3. Backend limitations ON + limit>0: Allow access with add button, show upgrade when limit reached * * These rules apply to: residence, task, contractors, documents */ object SubscriptionHelper { /** * Result of a usage/access check * @param allowed Whether the action is allowed * @param triggerKey The upgrade trigger key to use if not allowed (null if allowed) */ data class UsageCheck(val allowed: Boolean, val triggerKey: String?) /** * Derive the current subscription tier from DataManager. * "pro" if the backend subscription has an active trial or a non-empty expiresAt (active paid plan), * "free" otherwise. */ val currentTier: String get() { val subscription = DataManager.subscription.value ?: return "free" if (subscription.trialActive) return "pro" val expiresAt = subscription.expiresAt return if (!expiresAt.isNullOrEmpty()) "pro" else "free" } /** * Whether the user has a premium (pro) subscription. * True when limitations are disabled (everyone gets full access) * OR when the user is on the pro tier. */ val isPremium: Boolean get() { val subscription = DataManager.subscription.value ?: return false // If limitations are disabled, everyone effectively has premium access if (!subscription.limitationsEnabled) return true return currentTier == "pro" } /** * Check if the user can purchase a subscription on the given platform. * If the user already has an active Pro subscription from a different platform, * purchasing on this platform is not allowed. * * @param currentPlatform The platform attempting the purchase ("ios", "android", or "stripe") * @return UsageCheck with allowed=false if already subscribed on another platform */ fun canPurchaseOnPlatform(currentPlatform: String): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(allowed = true, triggerKey = null) if (currentTier != "pro") return UsageCheck(allowed = true, triggerKey = null) val source = subscription.subscriptionSource if (source != null && source != currentPlatform) { return UsageCheck(allowed = false, triggerKey = "already_subscribed_other_platform") } return UsageCheck(allowed = true, triggerKey = null) } // ===== PROPERTY (RESIDENCE) ===== /** * Check if the user should see an upgrade view instead of the residences screen. * Returns true (blocked) only when limitations are ON and limit=0. */ fun isResidencesBlocked(): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(false, null) // Allow access while loading if (!subscription.limitationsEnabled) { return UsageCheck(false, null) // Limitations disabled, never block } val tier = currentTier if (tier == "pro") { return UsageCheck(false, null) // Pro users never blocked } val limit = subscription.limits[tier]?.properties // If limit is 0, block access entirely if (limit == 0) { return UsageCheck(true, "add_second_property") } return UsageCheck(false, null) // limit > 0 or unlimited, allow access } /** * Check if user can add a property (when trying to add, not for blocking the screen). * Used when limit > 0 and user has reached the limit. */ fun canAddProperty(currentCount: Int = 0): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(true, null) // Allow if no subscription data if (!subscription.limitationsEnabled) { return UsageCheck(true, null) // Limitations disabled, allow everything } val tier = currentTier if (tier == "pro") { return UsageCheck(true, null) // Pro tier gets unlimited access } // Get limit for current tier (null = unlimited) val limit = subscription.limits[tier]?.properties // null means unlimited if (limit == null) { return UsageCheck(true, null) } // If limit is 0, they shouldn't even be here (screen should be blocked) // But if they somehow are, block the add if (limit == 0) { return UsageCheck(false, "add_second_property") } // limit > 0: check if they've reached it if (currentCount >= limit) { return UsageCheck(false, "add_second_property") } return UsageCheck(true, null) } // ===== TASKS ===== /** * Check if the user should see an upgrade view instead of the tasks screen. * Returns true (blocked) only when limitations are ON and limit=0. */ fun isTasksBlocked(): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(false, null) // Allow access while loading if (!subscription.limitationsEnabled) { return UsageCheck(false, null) } val tier = currentTier if (tier == "pro") { return UsageCheck(false, null) } val limit = subscription.limits[tier]?.tasks if (limit == 0) { return UsageCheck(true, "add_11th_task") } return UsageCheck(false, null) } /** * Check if user can add a task (when trying to add). */ fun canAddTask(currentCount: Int = 0): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(true, null) if (!subscription.limitationsEnabled) { return UsageCheck(true, null) } val tier = currentTier if (tier == "pro") { return UsageCheck(true, null) } val limit = subscription.limits[tier]?.tasks if (limit == null) { return UsageCheck(true, null) // Unlimited } if (limit == 0) { return UsageCheck(false, "add_11th_task") } if (currentCount >= limit) { return UsageCheck(false, "add_11th_task") } return UsageCheck(true, null) } // ===== CONTRACTORS ===== /** * Check if the user should see an upgrade view instead of the contractors screen. * Returns true (blocked) only when limitations are ON and limit=0. */ fun isContractorsBlocked(): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(false, null) // Allow access while loading if (!subscription.limitationsEnabled) { return UsageCheck(false, null) } val tier = currentTier if (tier == "pro") { return UsageCheck(false, null) } val limit = subscription.limits[tier]?.contractors if (limit == 0) { return UsageCheck(true, "view_contractors") } return UsageCheck(false, null) } /** * Check if user can add a contractor (when trying to add). */ fun canAddContractor(currentCount: Int = 0): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(true, null) if (!subscription.limitationsEnabled) { return UsageCheck(true, null) } val tier = currentTier if (tier == "pro") { return UsageCheck(true, null) } val limit = subscription.limits[tier]?.contractors if (limit == null) { return UsageCheck(true, null) } if (limit == 0) { return UsageCheck(false, "view_contractors") } if (currentCount >= limit) { return UsageCheck(false, "view_contractors") } return UsageCheck(true, null) } // ===== DOCUMENTS ===== /** * Check if the user should see an upgrade view instead of the documents screen. * Returns true (blocked) only when limitations are ON and limit=0. */ fun isDocumentsBlocked(): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(false, null) // Allow access while loading if (!subscription.limitationsEnabled) { return UsageCheck(false, null) } val tier = currentTier if (tier == "pro") { return UsageCheck(false, null) } val limit = subscription.limits[tier]?.documents if (limit == 0) { return UsageCheck(true, "view_documents") } return UsageCheck(false, null) } /** * Check if user can add a document (when trying to add). */ fun canAddDocument(currentCount: Int = 0): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(true, null) if (!subscription.limitationsEnabled) { return UsageCheck(true, null) } val tier = currentTier if (tier == "pro") { return UsageCheck(true, null) } val limit = subscription.limits[tier]?.documents if (limit == null) { return UsageCheck(true, null) } if (limit == 0) { return UsageCheck(false, "view_documents") } if (currentCount >= limit) { return UsageCheck(false, "view_documents") } return UsageCheck(true, null) } // ===== RESIDENCE SHARING ===== /** * Check if user can share a residence (Pro feature). * Returns true (blocked) when limitations are ON and user is not pro. */ fun canShareResidence(): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(true, null) // Allow if no subscription data (fallback) if (!subscription.limitationsEnabled) { return UsageCheck(true, null) // Limitations disabled, allow everything } if (currentTier == "pro") { return UsageCheck(true, null) // Pro tier can share } // Free users cannot share residences return UsageCheck(false, "share_residence") } // ===== CONTRACTOR SHARING ===== /** * Check if user can share a contractor (Pro feature). * Returns true (blocked) when limitations are ON and user is not pro. */ fun canShareContractor(): UsageCheck { val subscription = DataManager.subscription.value ?: return UsageCheck(true, null) // Allow if no subscription data (fallback) if (!subscription.limitationsEnabled) { return UsageCheck(true, null) // Limitations disabled, allow everything } if (currentTier == "pro") { return UsageCheck(true, null) // Pro tier can share } // Free users cannot share contractors return UsageCheck(false, "share_contractor") } // ===== DEPRECATED - Keep for backwards compatibility ===== @Deprecated("Use isContractorsBlocked() instead", ReplaceWith("isContractorsBlocked()")) fun shouldShowUpgradePromptForContractors(): UsageCheck = isContractorsBlocked() @Deprecated("Use isDocumentsBlocked() instead", ReplaceWith("isDocumentsBlocked()")) fun shouldShowUpgradePromptForDocuments(): UsageCheck = isDocumentsBlocked() }