mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-14 09:27:41 +00:00
Guard downgrades via billing portal (#7813)
- add a reusable subscription tier ranking helper + unit test - send pricing-table downgrades to the generic billing portal until backend proration is fixed ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7813-Guard-downgrades-via-billing-portal-2da6d73d365081f0a202dd5699143332) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { getPlanRank, isPlanDowngrade } from './subscriptionTierRank'
|
||||
|
||||
describe('subscriptionTierRank', () => {
|
||||
it('returns consistent order for ranked plans', () => {
|
||||
const yearlyPro = getPlanRank({ tierKey: 'pro', billingCycle: 'yearly' })
|
||||
const monthlyStandard = getPlanRank({
|
||||
tierKey: 'standard',
|
||||
billingCycle: 'monthly'
|
||||
})
|
||||
|
||||
expect(yearlyPro).toBeLessThan(monthlyStandard)
|
||||
})
|
||||
|
||||
it('identifies downgrades correctly', () => {
|
||||
const result = isPlanDowngrade({
|
||||
current: { tierKey: 'pro', billingCycle: 'yearly' },
|
||||
target: { tierKey: 'creator', billingCycle: 'monthly' }
|
||||
})
|
||||
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it('treats lateral or upgrade moves as non-downgrades', () => {
|
||||
expect(
|
||||
isPlanDowngrade({
|
||||
current: { tierKey: 'standard', billingCycle: 'monthly' },
|
||||
target: { tierKey: 'creator', billingCycle: 'monthly' }
|
||||
})
|
||||
).toBe(false)
|
||||
|
||||
expect(
|
||||
isPlanDowngrade({
|
||||
current: { tierKey: 'creator', billingCycle: 'monthly' },
|
||||
target: { tierKey: 'creator', billingCycle: 'monthly' }
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('treats unknown plans (e.g., founder) as non-downgrade cases', () => {
|
||||
const result = isPlanDowngrade({
|
||||
current: { tierKey: 'founder', billingCycle: 'monthly' },
|
||||
target: { tierKey: 'standard', billingCycle: 'monthly' }
|
||||
})
|
||||
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { TierKey } from '@/platform/cloud/subscription/constants/tierPricing'
|
||||
|
||||
export type BillingCycle = 'monthly' | 'yearly'
|
||||
|
||||
type RankedTierKey = Exclude<TierKey, 'founder'>
|
||||
type RankedPlanKey = `${BillingCycle}-${RankedTierKey}`
|
||||
|
||||
interface PlanDescriptor {
|
||||
tierKey: TierKey
|
||||
billingCycle: BillingCycle
|
||||
}
|
||||
|
||||
const PLAN_ORDER: RankedPlanKey[] = [
|
||||
'yearly-pro',
|
||||
'yearly-creator',
|
||||
'yearly-standard',
|
||||
'monthly-pro',
|
||||
'monthly-creator',
|
||||
'monthly-standard'
|
||||
]
|
||||
|
||||
const PLAN_RANK = PLAN_ORDER.reduce<Map<RankedPlanKey, number>>(
|
||||
(acc, plan, index) => acc.set(plan, index),
|
||||
new Map()
|
||||
)
|
||||
|
||||
const toRankedPlanKey = (
|
||||
tierKey: TierKey,
|
||||
billingCycle: BillingCycle
|
||||
): RankedPlanKey | null => {
|
||||
if (tierKey === 'founder') return null
|
||||
return `${billingCycle}-${tierKey}` as RankedPlanKey
|
||||
}
|
||||
|
||||
export const getPlanRank = ({
|
||||
tierKey,
|
||||
billingCycle
|
||||
}: PlanDescriptor): number => {
|
||||
const planKey = toRankedPlanKey(tierKey, billingCycle)
|
||||
if (!planKey) return Number.POSITIVE_INFINITY
|
||||
|
||||
return PLAN_RANK.get(planKey) ?? Number.POSITIVE_INFINITY
|
||||
}
|
||||
|
||||
interface DowngradeCheckParams {
|
||||
current: PlanDescriptor
|
||||
target: PlanDescriptor
|
||||
}
|
||||
|
||||
export const isPlanDowngrade = ({
|
||||
current,
|
||||
target
|
||||
}: DowngradeCheckParams): boolean => {
|
||||
const currentRank = getPlanRank(current)
|
||||
const targetRank = getPlanRank(target)
|
||||
|
||||
return targetRank > currentRank
|
||||
}
|
||||
Reference in New Issue
Block a user