Skip to content

Commit a1f31f0

Browse files
committed
feat(mobile): support iOS Apple subscriptions
1 parent 1d90f22 commit a1f31f0

File tree

12 files changed

+911
-165
lines changed

12 files changed

+911
-165
lines changed

apps/desktop/layer/renderer/src/modules/settings/tabs/plan.tsx

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,21 @@ import { useTranslation } from "react-i18next"
1313

1414
import type { PaymentFeature, PaymentPlan } from "~/atoms/server-configs"
1515
import { useIsPaymentEnabled, useServerConfigs } from "~/atoms/server-configs"
16+
import { followClient } from "~/lib/api-client"
1617
import { subscription } from "~/lib/auth"
1718

19+
const APPLE_SUBSCRIPTION_MANAGEMENT_URL = "https://apps.apple.com/account/subscriptions"
20+
21+
type ActiveSubscription = {
22+
source: "stripe" | "apple" | null
23+
plan: string | null
24+
status: string | null
25+
productId: string | null
26+
periodEnd: string | null
27+
trialEnd: string | null
28+
canManage: boolean
29+
}
30+
1831
const AI_MODEL_SELECTION_VALUE_LABELS = {
1932
none: {
2033
translationKey: "plan.featureValues.AI_MODEL_SELECTION.none",
@@ -94,25 +107,12 @@ const useUpgradePlan = ({ plan, annual }: { plan: string | undefined; annual: bo
94107
const useActiveSubscription = () => {
95108
const userId = useWhoami()?.id
96109
return useQuery({
97-
queryKey: ["activeSubscription"],
110+
queryKey: ["billingSubscription"],
98111
queryFn: async () => {
99-
const { data } = await subscription.list()
100-
// Find subscription: active, trialing, or canceled with valid period end
101-
return data?.find((sub) => {
102-
if (!sub.stripeSubscriptionId) return false
103-
104-
// Active or trialing subscriptions
105-
if (sub.status === "active" || sub.status === "trialing") {
106-
return true
107-
}
108-
109-
// Canceled subscriptions that haven't expired yet
110-
if (sub.status === "canceled" && sub.periodEnd) {
111-
return new Date(sub.periodEnd) > new Date()
112-
}
113-
114-
return false
115-
})
112+
const response = await followClient.request<{ code: number; data: ActiveSubscription }>(
113+
"/billing/subscription",
114+
)
115+
return response.data
116116
},
117117
enabled: !!userId,
118118
})
@@ -396,11 +396,13 @@ const PlanAction = ({
396396
const { t } = useTranslation("settings")
397397
const { data: activeSubscription } = useActiveSubscription()
398398
const billingPortalMutation = useBillingPortal()
399-
const canManageSubscription = !!activeSubscription?.stripeSubscriptionId
399+
const canManageSubscription = !!activeSubscription?.canManage
400+
const isAppleSubscription = activeSubscription?.source === "apple"
400401

401402
// Determine subscription status info
402403
const isCanceled = activeSubscription?.status === "canceled"
403-
const periodEnd = activeSubscription?.periodEnd ? new Date(activeSubscription.periodEnd) : null
404+
const periodEnd = activeSubscription?.trialEnd ?? activeSubscription?.periodEnd
405+
const effectivePeriodEnd = periodEnd ? new Date(periodEnd) : null
404406

405407
const getButtonConfig = () => {
406408
switch (actionType) {
@@ -486,22 +488,22 @@ const PlanAction = ({
486488
<span>
487489
{isCanceled
488490
? t("plan.canceled_expires", {
489-
date: periodEnd?.toLocaleDateString(undefined, {
491+
date: effectivePeriodEnd?.toLocaleDateString(undefined, {
490492
year: "numeric",
491493
month: "short",
492494
day: "numeric",
493495
}),
494496
})
495497
: activeSubscription?.status === "trialing"
496498
? t("plan.trial_ends", {
497-
date: periodEnd?.toLocaleDateString(undefined, {
499+
date: effectivePeriodEnd?.toLocaleDateString(undefined, {
498500
year: "numeric",
499501
month: "short",
500502
day: "numeric",
501503
}),
502504
})
503505
: t("plan.renews", {
504-
date: periodEnd?.toLocaleDateString(undefined, {
506+
date: effectivePeriodEnd?.toLocaleDateString(undefined, {
505507
year: "numeric",
506508
month: "short",
507509
day: "numeric",
@@ -519,7 +521,14 @@ const PlanAction = ({
519521
disabled={buttonConfig.disabled}
520522
onClick={
521523
actionType === "current" && canManageSubscription
522-
? () => billingPortalMutation.mutate()
524+
? () => {
525+
if (isAppleSubscription) {
526+
window.open(APPLE_SUBSCRIPTION_MANAGEMENT_URL, "_blank")
527+
return
528+
}
529+
530+
billingPortalMutation.mutate()
531+
}
523532
: onSelect
524533
}
525534
isLoading={isLoading || billingPortalMutation.isPending}

0 commit comments

Comments
 (0)