fix(mobile): simplify iOS subscription management (#4947)

This commit is contained in:
DIYgod 2026-04-02 15:27:21 +08:00 committed by GitHub
parent ce5e91962c
commit d27261d76e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 41 additions and 64 deletions

View File

@ -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 = ({
</Text>
</View>
)}
<Pressable
accessibilityRole="button"
onPress={onManageSubscription}
disabled={!canManageSubscription || isManaging}
className={cn(
"h-11 items-center justify-center rounded-full border border-opaque-separator/60",
!canManageSubscription && "opacity-50",
)}
>
{isManaging ? (
<ActivityIndicator />
) : (
<Text className="text-base font-medium text-label">
{canManageSubscription ? t("plan.manage_subscription") : t("plan.current_plan")}
</Text>
)}
</Pressable>
{isAppleSubscription ? (
<Text className="rounded-2xl bg-secondary-system-fill px-4 py-3 text-center text-sm leading-5 text-secondary-label">
{t("plan.manage_subscription_hint_apple")}
</Text>
) : (
<Pressable
accessibilityRole="button"
onPress={onManageSubscription}
disabled={!canManageSubscription || isManaging}
className={cn(
"h-11 items-center justify-center rounded-full border border-opaque-separator/60",
!canManageSubscription && "opacity-50",
)}
>
{isManaging ? (
<ActivityIndicator />
) : (
<Text className="text-base font-medium text-label">
{canManageSubscription ? t("plan.manage_subscription") : t("plan.current_plan")}
</Text>
)}
</Pressable>
)}
</View>
)
}

View File

@ -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<void>
restoreSubscriptionPurchases: () => Promise<void>
openSubscriptionManagement: () => Promise<void>
}
const AppleIAPContext = createContext<AppleIAPContextValue | null>(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<AppleIAPContextValue>(
() => ({
@ -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,
],
)

View File

@ -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",

View File

@ -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é",

View File

@ -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": "プライバシー",

View File

@ -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": "隐私政策",

View File

@ -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": "隱私政策",