mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +00:00
FirebaseUID gating pending purchases
This commit is contained in:
@@ -10,7 +10,8 @@ const {
|
|||||||
mockShowSubscriptionRequiredDialog,
|
mockShowSubscriptionRequiredDialog,
|
||||||
mockGetAuthHeader,
|
mockGetAuthHeader,
|
||||||
mockPushDataLayerEvent,
|
mockPushDataLayerEvent,
|
||||||
mockTelemetry
|
mockTelemetry,
|
||||||
|
mockUserId
|
||||||
} = vi.hoisted(() => ({
|
} = vi.hoisted(() => ({
|
||||||
mockIsLoggedIn: { value: false },
|
mockIsLoggedIn: { value: false },
|
||||||
mockReportError: vi.fn(),
|
mockReportError: vi.fn(),
|
||||||
@@ -23,7 +24,8 @@ const {
|
|||||||
mockTelemetry: {
|
mockTelemetry: {
|
||||||
trackSubscription: vi.fn(),
|
trackSubscription: vi.fn(),
|
||||||
trackMonthlySubscriptionCancelled: vi.fn()
|
trackMonthlySubscriptionCancelled: vi.fn()
|
||||||
}
|
},
|
||||||
|
mockUserId: { value: 'user-123' }
|
||||||
}))
|
}))
|
||||||
|
|
||||||
let scope: ReturnType<typeof effectScope> | undefined
|
let scope: ReturnType<typeof effectScope> | undefined
|
||||||
@@ -89,7 +91,8 @@ vi.mock('@/services/dialogService', () => ({
|
|||||||
|
|
||||||
vi.mock('@/stores/firebaseAuthStore', () => ({
|
vi.mock('@/stores/firebaseAuthStore', () => ({
|
||||||
useFirebaseAuthStore: vi.fn(() => ({
|
useFirebaseAuthStore: vi.fn(() => ({
|
||||||
getFirebaseAuthHeader: mockGetAuthHeader
|
getFirebaseAuthHeader: mockGetAuthHeader,
|
||||||
|
userId: mockUserId.value
|
||||||
})),
|
})),
|
||||||
FirebaseAuthStoreError: class extends Error {}
|
FirebaseAuthStoreError: class extends Error {}
|
||||||
}))
|
}))
|
||||||
@@ -112,6 +115,7 @@ describe('useSubscription', () => {
|
|||||||
mockTelemetry.trackSubscription.mockReset()
|
mockTelemetry.trackSubscription.mockReset()
|
||||||
mockTelemetry.trackMonthlySubscriptionCancelled.mockReset()
|
mockTelemetry.trackMonthlySubscriptionCancelled.mockReset()
|
||||||
mockPushDataLayerEvent.mockReset()
|
mockPushDataLayerEvent.mockReset()
|
||||||
|
mockUserId.value = 'user-123'
|
||||||
mockPushDataLayerEvent.mockImplementation((event) => {
|
mockPushDataLayerEvent.mockImplementation((event) => {
|
||||||
const dataLayer = window.dataLayer ?? (window.dataLayer = [])
|
const dataLayer = window.dataLayer ?? (window.dataLayer = [])
|
||||||
dataLayer.push(event)
|
dataLayer.push(event)
|
||||||
@@ -249,6 +253,7 @@ describe('useSubscription', () => {
|
|||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'pending_subscription_purchase',
|
'pending_subscription_purchase',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
firebaseUid: 'user-123',
|
||||||
tierKey: 'creator',
|
tierKey: 'creator',
|
||||||
billingCycle: 'monthly',
|
billingCycle: 'monthly',
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
@@ -287,6 +292,38 @@ describe('useSubscription', () => {
|
|||||||
expect(localStorage.getItem('pending_subscription_purchase')).toBeNull()
|
expect(localStorage.getItem('pending_subscription_purchase')).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('ignores pending purchase when user does not match', async () => {
|
||||||
|
window.dataLayer = []
|
||||||
|
localStorage.setItem(
|
||||||
|
'pending_subscription_purchase',
|
||||||
|
JSON.stringify({
|
||||||
|
firebaseUid: 'user-123',
|
||||||
|
tierKey: 'creator',
|
||||||
|
billingCycle: 'monthly',
|
||||||
|
timestamp: Date.now()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
mockUserId.value = 'user-456'
|
||||||
|
vi.mocked(global.fetch).mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
is_active: true,
|
||||||
|
subscription_id: 'sub_123',
|
||||||
|
subscription_tier: 'CREATOR',
|
||||||
|
subscription_duration: 'MONTHLY'
|
||||||
|
})
|
||||||
|
} as Response)
|
||||||
|
|
||||||
|
mockIsLoggedIn.value = true
|
||||||
|
const { fetchStatus } = useSubscriptionWithScope()
|
||||||
|
|
||||||
|
await fetchStatus()
|
||||||
|
|
||||||
|
expect(window.dataLayer).toHaveLength(0)
|
||||||
|
expect(localStorage.getItem('pending_subscription_purchase')).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
it('should handle fetch errors gracefully', async () => {
|
it('should handle fetch errors gracefully', async () => {
|
||||||
vi.mocked(global.fetch).mockResolvedValue({
|
vi.mocked(global.fetch).mockResolvedValue({
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function useSubscriptionInternal() {
|
|||||||
const { reportError, accessBillingPortal } = useFirebaseAuthActions()
|
const { reportError, accessBillingPortal } = useFirebaseAuthActions()
|
||||||
const { showSubscriptionRequiredDialog } = useDialogService()
|
const { showSubscriptionRequiredDialog } = useDialogService()
|
||||||
|
|
||||||
const { getFirebaseAuthHeader } = useFirebaseAuthStore()
|
const { getFirebaseAuthHeader, userId } = useFirebaseAuthStore()
|
||||||
const { wrapWithErrorHandlingAsync } = useErrorHandling()
|
const { wrapWithErrorHandlingAsync } = useErrorHandling()
|
||||||
|
|
||||||
const { isLoggedIn } = useCurrentUser()
|
const { isLoggedIn } = useCurrentUser()
|
||||||
@@ -109,7 +109,9 @@ function useSubscriptionInternal() {
|
|||||||
): void {
|
): void {
|
||||||
if (!status?.is_active || !status.subscription_id) return
|
if (!status?.is_active || !status.subscription_id) return
|
||||||
|
|
||||||
const pendingPurchase = getPendingSubscriptionPurchase()
|
if (!userId) return
|
||||||
|
|
||||||
|
const pendingPurchase = getPendingSubscriptionPurchase(userId)
|
||||||
if (!pendingPurchase) return
|
if (!pendingPurchase) return
|
||||||
|
|
||||||
const { tierKey, billingCycle } = pendingPurchase
|
const { tierKey, billingCycle } = pendingPurchase
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export async function performSubscriptionCheckout(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!isCloud) return
|
if (!isCloud) return
|
||||||
|
|
||||||
const { getFirebaseAuthHeader } = useFirebaseAuthStore()
|
const { getFirebaseAuthHeader, userId } = useFirebaseAuthStore()
|
||||||
const authHeader = await getFirebaseAuthHeader()
|
const authHeader = await getFirebaseAuthHeader()
|
||||||
|
|
||||||
if (!authHeader) {
|
if (!authHeader) {
|
||||||
@@ -79,7 +79,9 @@ export async function performSubscriptionCheckout(
|
|||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
if (data.checkout_url) {
|
if (data.checkout_url) {
|
||||||
startSubscriptionPurchaseTracking(tierKey, currentBillingCycle)
|
if (userId) {
|
||||||
|
startSubscriptionPurchaseTracking(tierKey, currentBillingCycle, userId)
|
||||||
|
}
|
||||||
if (openInNewTab) {
|
if (openInNewTab) {
|
||||||
window.open(data.checkout_url, '_blank')
|
window.open(data.checkout_url, '_blank')
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { TierKey } from '@/platform/cloud/subscription/constants/tierPricin
|
|||||||
import type { BillingCycle } from './subscriptionTierRank'
|
import type { BillingCycle } from './subscriptionTierRank'
|
||||||
|
|
||||||
type PendingSubscriptionPurchase = {
|
type PendingSubscriptionPurchase = {
|
||||||
|
firebaseUid: string
|
||||||
tierKey: TierKey
|
tierKey: TierKey
|
||||||
billingCycle: BillingCycle
|
billingCycle: BillingCycle
|
||||||
timestamp: number
|
timestamp: number
|
||||||
@@ -22,11 +23,14 @@ const safeRemove = (): void => {
|
|||||||
|
|
||||||
export function startSubscriptionPurchaseTracking(
|
export function startSubscriptionPurchaseTracking(
|
||||||
tierKey: TierKey,
|
tierKey: TierKey,
|
||||||
billingCycle: BillingCycle
|
billingCycle: BillingCycle,
|
||||||
|
firebaseUid: string
|
||||||
): void {
|
): void {
|
||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
|
if (!firebaseUid) return
|
||||||
try {
|
try {
|
||||||
const payload: PendingSubscriptionPurchase = {
|
const payload: PendingSubscriptionPurchase = {
|
||||||
|
firebaseUid,
|
||||||
tierKey,
|
tierKey,
|
||||||
billingCycle,
|
billingCycle,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
@@ -37,8 +41,11 @@ export function startSubscriptionPurchaseTracking(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPendingSubscriptionPurchase(): PendingSubscriptionPurchase | null {
|
export function getPendingSubscriptionPurchase(
|
||||||
|
firebaseUid: string
|
||||||
|
): PendingSubscriptionPurchase | null {
|
||||||
if (typeof window === 'undefined') return null
|
if (typeof window === 'undefined') return null
|
||||||
|
if (!firebaseUid) return null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(STORAGE_KEY)
|
const raw = localStorage.getItem(STORAGE_KEY)
|
||||||
@@ -50,8 +57,9 @@ export function getPendingSubscriptionPurchase(): PendingSubscriptionPurchase |
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tierKey, billingCycle, timestamp } = parsed
|
const { firebaseUid: storedUid, tierKey, billingCycle, timestamp } = parsed
|
||||||
if (
|
if (
|
||||||
|
storedUid !== firebaseUid ||
|
||||||
!VALID_TIERS.includes(tierKey) ||
|
!VALID_TIERS.includes(tierKey) ||
|
||||||
!VALID_CYCLES.includes(billingCycle) ||
|
!VALID_CYCLES.includes(billingCycle) ||
|
||||||
typeof timestamp !== 'number'
|
typeof timestamp !== 'number'
|
||||||
|
|||||||
Reference in New Issue
Block a user