From d27261d76e71f594f71d3ef35e8ef7060e9b7191 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Thu, 2 Apr 2026 15:27:21 +0800 Subject: [PATCH] fix(mobile): simplify iOS subscription management (#4947) --- .../src/modules/settings/routes/Plan.tsx | 51 +++++++++---------- .../mobile/src/providers/AppleIAPProvider.tsx | 49 ++++-------------- locales/settings/en.json | 1 + locales/settings/fr-FR.json | 1 + locales/settings/ja.json | 1 + locales/settings/zh-CN.json | 1 + locales/settings/zh-TW.json | 1 + 7 files changed, 41 insertions(+), 64 deletions(-) diff --git a/apps/mobile/src/modules/settings/routes/Plan.tsx b/apps/mobile/src/modules/settings/routes/Plan.tsx index 542db97fd..1aa015f4b 100644 --- a/apps/mobile/src/modules/settings/routes/Plan.tsx +++ b/apps/mobile/src/modules/settings/routes/Plan.tsx @@ -316,7 +316,6 @@ export const PlanScreen: NavigationControllerView = () => { isPurchasing, isRestoring, loadSubscriptions, - openSubscriptionManagement, requestSubscriptionPurchase, restoreSubscriptionPurchases, } = useAppleIAP() @@ -485,15 +484,8 @@ export const PlanScreen: NavigationControllerView = () => { }) const handleManageSubscription = useCallback(() => { - if (activeSubscription?.source === "apple") { - void openSubscriptionManagement().catch(() => { - toast.error(t("subscription.actions.manage_error")) - }) - return - } - billingPortalMutation.mutate() - }, [activeSubscription?.source, billingPortalMutation, openSubscriptionManagement, t]) + }, [billingPortalMutation]) if (!isPaymentEnabled || sortedPlans.length === 0) { return ( @@ -971,6 +963,7 @@ const PlanAction = ({ const { t } = useTranslation("settings") const canManageSubscription = !!activeSubscription?.canManage + const isAppleSubscription = activeSubscription?.source === "apple" const isCanceled = activeSubscription?.status === "canceled" const periodEnd = activeSubscription?.trialEnd ?? activeSubscription?.periodEnd const effectivePeriodEnd = periodEnd ? new Date(periodEnd) : null @@ -1025,23 +1018,29 @@ const PlanAction = ({ )} - - {isManaging ? ( - - ) : ( - - {canManageSubscription ? t("plan.manage_subscription") : t("plan.current_plan")} - - )} - + {isAppleSubscription ? ( + + {t("plan.manage_subscription_hint_apple")} + + ) : ( + + {isManaging ? ( + + ) : ( + + {canManageSubscription ? t("plan.manage_subscription") : t("plan.current_plan")} + + )} + + )} ) } diff --git a/apps/mobile/src/providers/AppleIAPProvider.tsx b/apps/mobile/src/providers/AppleIAPProvider.tsx index 35b7a8053..a5b75cec0 100644 --- a/apps/mobile/src/providers/AppleIAPProvider.tsx +++ b/apps/mobile/src/providers/AppleIAPProvider.tsx @@ -2,8 +2,7 @@ import { whoamiQueryKey } from "@follow/store/user/hooks" import { userSyncService } from "@follow/store/user/store" import { requireNativeModule } from "expo" import type { ProductPurchase, SubscriptionProduct } from "expo-iap" -import { getTransactionJws, showManageSubscriptions, useIAP } from "expo-iap" -import { openURL } from "expo-linking" +import { getTransactionJws, useIAP } from "expo-iap" import type { PropsWithChildren } from "react" import { createContext, use, useCallback, useEffect, useMemo, useRef, useState } from "react" import { useTranslation } from "react-i18next" @@ -15,7 +14,6 @@ import { proxyEnv } from "@/src/lib/proxy-env" import { queryClient } from "@/src/lib/query-client" import { toast } from "@/src/lib/toast" -const APPLE_SUBSCRIPTION_MANAGEMENT_URL = "https://apps.apple.com/account/subscriptions" const billingSubscriptionQueryKey = ["billingSubscription"] type BillingSubscriptionResponse = { @@ -40,12 +38,17 @@ type AppleIAPContextValue = { appAccountToken?: string | null }) => Promise restoreSubscriptionPurchases: () => Promise - openSubscriptionManagement: () => Promise } const AppleIAPContext = createContext(null) -const noopAsync = async () => {} +const refreshBillingState = async () => { + await Promise.allSettled([ + userSyncService.whoami(), + queryClient.invalidateQueries({ queryKey: whoamiQueryKey }), + queryClient.invalidateQueries({ queryKey: billingSubscriptionQueryKey }), + ]) +} export const AppleIAPProvider = ({ children }: PropsWithChildren) => { const { t } = useTranslation("settings") @@ -107,14 +110,6 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => { availablePurchasesRef.current = availablePurchases }, [availablePurchases]) - const refreshBillingState = useCallback(async () => { - await Promise.allSettled([ - userSyncService.whoami(), - queryClient.invalidateQueries({ queryKey: whoamiQueryKey }), - queryClient.invalidateQueries({ queryKey: billingSubscriptionQueryKey }), - ]) - }, []) - const verifyPurchase = useCallback( async (purchase: ProductPurchase) => { const productId = purchase.id @@ -186,14 +181,7 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => { setIsProcessingPurchase(false) } })() - }, [ - currentPurchase, - finishTransaction, - knownSubscriptionIds, - refreshBillingState, - t, - verifyPurchase, - ]) + }, [currentPurchase, finishTransaction, knownSubscriptionIds, t, verifyPurchase]) useEffect(() => { if (!currentPurchaseError) { @@ -267,7 +255,7 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => { throw error } }, - [refreshBillingState, requestPurchase, storeKitTestHelper], + [requestPurchase, storeKitTestHelper], ) const restoreSubscriptionPurchases = useCallback(async () => { @@ -322,20 +310,7 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => { } finally { setIsRestoring(false) } - }, [knownSubscriptionIds, refreshBillingState, restorePurchases, t, verifyPurchase]) - - const openSubscriptionManagement = useCallback(async () => { - if (Platform.OS !== "ios") { - await noopAsync() - return - } - - try { - await showManageSubscriptions() - } catch { - await openURL(APPLE_SUBSCRIPTION_MANAGEMENT_URL) - } - }, []) + }, [knownSubscriptionIds, restorePurchases, t, verifyPurchase]) const contextValue = useMemo( () => ({ @@ -347,7 +322,6 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => { loadSubscriptions, requestSubscriptionPurchase, restoreSubscriptionPurchases, - openSubscriptionManagement, }), [ connected, @@ -358,7 +332,6 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => { loadSubscriptions, requestSubscriptionPurchase, restoreSubscriptionPurchases, - openSubscriptionManagement, ], ) diff --git a/locales/settings/en.json b/locales/settings/en.json index 8be6da422..600f8be5f 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -615,6 +615,7 @@ "plan.features.PRIVATE_SUBSCRIPTION": "Private Subscriptions", "plan.features.SECURE_IMAGE_PROXY": "Secure Image Proxy", "plan.manage_subscription": "Manage Subscription", + "plan.manage_subscription_hint_apple": "Manage: Apple Account > Subscriptions", "plan.renews": "Renews {{date}}", "plan.trial_ends": "Trial ends {{date}}", "privacy.privacy": "Privacy", diff --git a/locales/settings/fr-FR.json b/locales/settings/fr-FR.json index db7f15002..9d36ee9f8 100644 --- a/locales/settings/fr-FR.json +++ b/locales/settings/fr-FR.json @@ -615,6 +615,7 @@ "plan.features.PRIVATE_SUBSCRIPTION": "Abonnements privés", "plan.features.SECURE_IMAGE_PROXY": "Proxy d'image sécurisé", "plan.manage_subscription": "Gérer l'abonnement", + "plan.manage_subscription_hint_apple": "Gérer : Compte Apple > Abonnements", "plan.renews": "Renouvellement le {{date}}", "plan.trial_ends": "L'essai se termine le {{date}}", "privacy.privacy": "Confidentialité", diff --git a/locales/settings/ja.json b/locales/settings/ja.json index 0c6b01ab0..9c235141b 100644 --- a/locales/settings/ja.json +++ b/locales/settings/ja.json @@ -615,6 +615,7 @@ "plan.features.PRIVATE_SUBSCRIPTION": "プライベートサブスクリプション", "plan.features.SECURE_IMAGE_PROXY": "セキュア画像プロキシ", "plan.manage_subscription": "サブスクリプションを管理", + "plan.manage_subscription_hint_apple": "管理:Apple Account > サブスクリプション", "plan.renews": "更新日 {{date}}", "plan.trial_ends": "トライアル終了日 {{date}}", "privacy.privacy": "プライバシー", diff --git a/locales/settings/zh-CN.json b/locales/settings/zh-CN.json index 1aac46598..4072df4fe 100644 --- a/locales/settings/zh-CN.json +++ b/locales/settings/zh-CN.json @@ -615,6 +615,7 @@ "plan.features.PRIVATE_SUBSCRIPTION": "私有订阅", "plan.features.SECURE_IMAGE_PROXY": "安全图片代理", "plan.manage_subscription": "管理订阅", + "plan.manage_subscription_hint_apple": "管理:Apple 账户 > 订阅", "plan.renews": "续订日期 {{date}}", "plan.trial_ends": "试用期至 {{date}}", "privacy.privacy": "隐私政策", diff --git a/locales/settings/zh-TW.json b/locales/settings/zh-TW.json index b401ccfc3..1e53bde73 100644 --- a/locales/settings/zh-TW.json +++ b/locales/settings/zh-TW.json @@ -615,6 +615,7 @@ "plan.features.PRIVATE_SUBSCRIPTION": "私有訂閱", "plan.features.SECURE_IMAGE_PROXY": "安全圖片代理", "plan.manage_subscription": "管理訂閱", + "plan.manage_subscription_hint_apple": "管理:Apple 帳號 > 訂閱", "plan.renews": "續訂日期 {{date}}", "plan.trial_ends": "試用期至 {{date}}", "privacy.privacy": "隱私政策",