Skip to content

Commit d27261d

Browse files
authored
fix(mobile): simplify iOS subscription management (#4947)
1 parent ce5e919 commit d27261d

7 files changed

Lines changed: 41 additions & 64 deletions

File tree

apps/mobile/src/modules/settings/routes/Plan.tsx

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,6 @@ export const PlanScreen: NavigationControllerView = () => {
316316
isPurchasing,
317317
isRestoring,
318318
loadSubscriptions,
319-
openSubscriptionManagement,
320319
requestSubscriptionPurchase,
321320
restoreSubscriptionPurchases,
322321
} = useAppleIAP()
@@ -485,15 +484,8 @@ export const PlanScreen: NavigationControllerView = () => {
485484
})
486485

487486
const handleManageSubscription = useCallback(() => {
488-
if (activeSubscription?.source === "apple") {
489-
void openSubscriptionManagement().catch(() => {
490-
toast.error(t("subscription.actions.manage_error"))
491-
})
492-
return
493-
}
494-
495487
billingPortalMutation.mutate()
496-
}, [activeSubscription?.source, billingPortalMutation, openSubscriptionManagement, t])
488+
}, [billingPortalMutation])
497489

498490
if (!isPaymentEnabled || sortedPlans.length === 0) {
499491
return (
@@ -971,6 +963,7 @@ const PlanAction = ({
971963
const { t } = useTranslation("settings")
972964

973965
const canManageSubscription = !!activeSubscription?.canManage
966+
const isAppleSubscription = activeSubscription?.source === "apple"
974967
const isCanceled = activeSubscription?.status === "canceled"
975968
const periodEnd = activeSubscription?.trialEnd ?? activeSubscription?.periodEnd
976969
const effectivePeriodEnd = periodEnd ? new Date(periodEnd) : null
@@ -1025,23 +1018,29 @@ const PlanAction = ({
10251018
</Text>
10261019
</View>
10271020
)}
1028-
<Pressable
1029-
accessibilityRole="button"
1030-
onPress={onManageSubscription}
1031-
disabled={!canManageSubscription || isManaging}
1032-
className={cn(
1033-
"h-11 items-center justify-center rounded-full border border-opaque-separator/60",
1034-
!canManageSubscription && "opacity-50",
1035-
)}
1036-
>
1037-
{isManaging ? (
1038-
<ActivityIndicator />
1039-
) : (
1040-
<Text className="text-base font-medium text-label">
1041-
{canManageSubscription ? t("plan.manage_subscription") : t("plan.current_plan")}
1042-
</Text>
1043-
)}
1044-
</Pressable>
1021+
{isAppleSubscription ? (
1022+
<Text className="rounded-2xl bg-secondary-system-fill px-4 py-3 text-center text-sm leading-5 text-secondary-label">
1023+
{t("plan.manage_subscription_hint_apple")}
1024+
</Text>
1025+
) : (
1026+
<Pressable
1027+
accessibilityRole="button"
1028+
onPress={onManageSubscription}
1029+
disabled={!canManageSubscription || isManaging}
1030+
className={cn(
1031+
"h-11 items-center justify-center rounded-full border border-opaque-separator/60",
1032+
!canManageSubscription && "opacity-50",
1033+
)}
1034+
>
1035+
{isManaging ? (
1036+
<ActivityIndicator />
1037+
) : (
1038+
<Text className="text-base font-medium text-label">
1039+
{canManageSubscription ? t("plan.manage_subscription") : t("plan.current_plan")}
1040+
</Text>
1041+
)}
1042+
</Pressable>
1043+
)}
10451044
</View>
10461045
)
10471046
}

apps/mobile/src/providers/AppleIAPProvider.tsx

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { whoamiQueryKey } from "@follow/store/user/hooks"
22
import { userSyncService } from "@follow/store/user/store"
33
import { requireNativeModule } from "expo"
44
import type { ProductPurchase, SubscriptionProduct } from "expo-iap"
5-
import { getTransactionJws, showManageSubscriptions, useIAP } from "expo-iap"
6-
import { openURL } from "expo-linking"
5+
import { getTransactionJws, useIAP } from "expo-iap"
76
import type { PropsWithChildren } from "react"
87
import { createContext, use, useCallback, useEffect, useMemo, useRef, useState } from "react"
98
import { useTranslation } from "react-i18next"
@@ -15,7 +14,6 @@ import { proxyEnv } from "@/src/lib/proxy-env"
1514
import { queryClient } from "@/src/lib/query-client"
1615
import { toast } from "@/src/lib/toast"
1716

18-
const APPLE_SUBSCRIPTION_MANAGEMENT_URL = "https://apps.apple.com/account/subscriptions"
1917
const billingSubscriptionQueryKey = ["billingSubscription"]
2018

2119
type BillingSubscriptionResponse = {
@@ -40,12 +38,17 @@ type AppleIAPContextValue = {
4038
appAccountToken?: string | null
4139
}) => Promise<void>
4240
restoreSubscriptionPurchases: () => Promise<void>
43-
openSubscriptionManagement: () => Promise<void>
4441
}
4542

4643
const AppleIAPContext = createContext<AppleIAPContextValue | null>(null)
4744

48-
const noopAsync = async () => {}
45+
const refreshBillingState = async () => {
46+
await Promise.allSettled([
47+
userSyncService.whoami(),
48+
queryClient.invalidateQueries({ queryKey: whoamiQueryKey }),
49+
queryClient.invalidateQueries({ queryKey: billingSubscriptionQueryKey }),
50+
])
51+
}
4952

5053
export const AppleIAPProvider = ({ children }: PropsWithChildren) => {
5154
const { t } = useTranslation("settings")
@@ -107,14 +110,6 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => {
107110
availablePurchasesRef.current = availablePurchases
108111
}, [availablePurchases])
109112

110-
const refreshBillingState = useCallback(async () => {
111-
await Promise.allSettled([
112-
userSyncService.whoami(),
113-
queryClient.invalidateQueries({ queryKey: whoamiQueryKey }),
114-
queryClient.invalidateQueries({ queryKey: billingSubscriptionQueryKey }),
115-
])
116-
}, [])
117-
118113
const verifyPurchase = useCallback(
119114
async (purchase: ProductPurchase) => {
120115
const productId = purchase.id
@@ -186,14 +181,7 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => {
186181
setIsProcessingPurchase(false)
187182
}
188183
})()
189-
}, [
190-
currentPurchase,
191-
finishTransaction,
192-
knownSubscriptionIds,
193-
refreshBillingState,
194-
t,
195-
verifyPurchase,
196-
])
184+
}, [currentPurchase, finishTransaction, knownSubscriptionIds, t, verifyPurchase])
197185

198186
useEffect(() => {
199187
if (!currentPurchaseError) {
@@ -267,7 +255,7 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => {
267255
throw error
268256
}
269257
},
270-
[refreshBillingState, requestPurchase, storeKitTestHelper],
258+
[requestPurchase, storeKitTestHelper],
271259
)
272260

273261
const restoreSubscriptionPurchases = useCallback(async () => {
@@ -322,20 +310,7 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => {
322310
} finally {
323311
setIsRestoring(false)
324312
}
325-
}, [knownSubscriptionIds, refreshBillingState, restorePurchases, t, verifyPurchase])
326-
327-
const openSubscriptionManagement = useCallback(async () => {
328-
if (Platform.OS !== "ios") {
329-
await noopAsync()
330-
return
331-
}
332-
333-
try {
334-
await showManageSubscriptions()
335-
} catch {
336-
await openURL(APPLE_SUBSCRIPTION_MANAGEMENT_URL)
337-
}
338-
}, [])
313+
}, [knownSubscriptionIds, restorePurchases, t, verifyPurchase])
339314

340315
const contextValue = useMemo<AppleIAPContextValue>(
341316
() => ({
@@ -347,7 +322,6 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => {
347322
loadSubscriptions,
348323
requestSubscriptionPurchase,
349324
restoreSubscriptionPurchases,
350-
openSubscriptionManagement,
351325
}),
352326
[
353327
connected,
@@ -358,7 +332,6 @@ export const AppleIAPProvider = ({ children }: PropsWithChildren) => {
358332
loadSubscriptions,
359333
requestSubscriptionPurchase,
360334
restoreSubscriptionPurchases,
361-
openSubscriptionManagement,
362335
],
363336
)
364337

locales/settings/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@
615615
"plan.features.PRIVATE_SUBSCRIPTION": "Private Subscriptions",
616616
"plan.features.SECURE_IMAGE_PROXY": "Secure Image Proxy",
617617
"plan.manage_subscription": "Manage Subscription",
618+
"plan.manage_subscription_hint_apple": "Manage: Apple Account > Subscriptions",
618619
"plan.renews": "Renews {{date}}",
619620
"plan.trial_ends": "Trial ends {{date}}",
620621
"privacy.privacy": "Privacy",

locales/settings/fr-FR.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@
615615
"plan.features.PRIVATE_SUBSCRIPTION": "Abonnements privés",
616616
"plan.features.SECURE_IMAGE_PROXY": "Proxy d'image sécurisé",
617617
"plan.manage_subscription": "Gérer l'abonnement",
618+
"plan.manage_subscription_hint_apple": "Gérer : Compte Apple > Abonnements",
618619
"plan.renews": "Renouvellement le {{date}}",
619620
"plan.trial_ends": "L'essai se termine le {{date}}",
620621
"privacy.privacy": "Confidentialité",

locales/settings/ja.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@
615615
"plan.features.PRIVATE_SUBSCRIPTION": "プライベートサブスクリプション",
616616
"plan.features.SECURE_IMAGE_PROXY": "セキュア画像プロキシ",
617617
"plan.manage_subscription": "サブスクリプションを管理",
618+
"plan.manage_subscription_hint_apple": "管理:Apple Account > サブスクリプション",
618619
"plan.renews": "更新日 {{date}}",
619620
"plan.trial_ends": "トライアル終了日 {{date}}",
620621
"privacy.privacy": "プライバシー",

locales/settings/zh-CN.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@
615615
"plan.features.PRIVATE_SUBSCRIPTION": "私有订阅",
616616
"plan.features.SECURE_IMAGE_PROXY": "安全图片代理",
617617
"plan.manage_subscription": "管理订阅",
618+
"plan.manage_subscription_hint_apple": "管理:Apple 账户 > 订阅",
618619
"plan.renews": "续订日期 {{date}}",
619620
"plan.trial_ends": "试用期至 {{date}}",
620621
"privacy.privacy": "隐私政策",

locales/settings/zh-TW.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@
615615
"plan.features.PRIVATE_SUBSCRIPTION": "私有訂閱",
616616
"plan.features.SECURE_IMAGE_PROXY": "安全圖片代理",
617617
"plan.manage_subscription": "管理訂閱",
618+
"plan.manage_subscription_hint_apple": "管理:Apple 帳號 > 訂閱",
618619
"plan.renews": "續訂日期 {{date}}",
619620
"plan.trial_ends": "試用期至 {{date}}",
620621
"privacy.privacy": "隱私政策",

0 commit comments

Comments
 (0)