import { useCallback } from 'react' import { createLogger } from '@sim/logger' import { isOrgAdminRole } from '@sim/platform-authz/predicates' import { getErrorMessage } from '@sim/utils/errors' import { useQueryClient } from '@tanstack/react-query' import { ApiClientError } from '@/lib/api/client/errors' import { requestJson } from '@/lib/api/client/request' import { listCreatorOrganizationsContract } from '@/lib/api/contracts/organizations' import { subscriptionTransferContract } from '@/lib/api/contracts/user' import { client, useSession, useSubscription } from '@/lib/auth/auth-client' import { buildPlanName, getDisplayPlanName, isPaid } from '@/lib/billing/plan-helpers' import { hasPaidSubscriptionStatus } from '@/lib/billing/subscriptions/utils' import { organizationKeys } from '@/hooks/queries/organization' const logger = createLogger('SubscriptionUpgrade') type TargetPlan = 'pro' | 'team' const CONSTANTS = { INITIAL_TEAM_SEATS: 1, DEFAULT_CREDIT_TIER: 6000, } as const interface UpgradeOptions { creditTier?: number annual?: boolean } export function useSubscriptionUpgrade() { const { data: session } = useSession() const betterAuthSubscription = useSubscription() const queryClient = useQueryClient() const handleUpgrade = useCallback( async (targetPlan: TargetPlan, options?: UpgradeOptions) => { const creditTier = options?.creditTier ?? CONSTANTS.DEFAULT_CREDIT_TIER const annual = options?.annual ?? false const planName = buildPlanName(targetPlan, creditTier) const userId = session?.user?.id if (!userId) { throw new Error('User not authenticated') } let currentSubscriptionRowId: string | undefined let currentStripeSubscriptionId: string | undefined let allSubscriptions: any[] = [] try { const listResult = await client.subscription.list() allSubscriptions = listResult.data || [] const activePersonalSub = allSubscriptions.find( (sub: any) => hasPaidSubscriptionStatus(sub.status) && sub.referenceId === userId ) currentSubscriptionRowId = activePersonalSub?.id currentStripeSubscriptionId = activePersonalSub?.stripeSubscriptionId } catch (_e) { currentSubscriptionRowId = undefined currentStripeSubscriptionId = undefined } if (currentSubscriptionRowId && !currentStripeSubscriptionId) { logger.error('Active paid subscription is missing its Stripe subscription ID', { userId, subscriptionRowId: currentSubscriptionRowId, targetPlan, }) throw new Error( 'We could not match your current plan with our payment provider. Please contact support before upgrading so you are not charged twice.' ) } let referenceId = userId if (targetPlan === 'team') { try { let orgsData try { orgsData = await requestJson(listCreatorOrganizationsContract, {}) } catch (err) { if (err instanceof ApiClientError) { throw new Error('Failed to check organization status') } throw err } const existingOrg = orgsData.organizations?.find((org) => isOrgAdminRole(org.role)) if (existingOrg) { const existingOrgSub = allSubscriptions.find( (sub: any) => hasPaidSubscriptionStatus(sub.status) && sub.referenceId === existingOrg.id && isPaid(sub.plan) ) if (existingOrgSub) { logger.warn('Organization already has an active subscription', { userId, organizationId: existingOrg.id, existingSubscriptionId: existingOrgSub.id, plan: existingOrgSub.plan, }) const existingPlanName = getDisplayPlanName(existingOrgSub.plan) throw new Error( `This organization is already on the ${existingPlanName} plan. Manage it from the billing settings.` ) } logger.info('Using existing organization for team plan upgrade', { userId, organizationId: existingOrg.id, }) referenceId = existingOrg.id try { await client.organization.setActive({ organizationId: referenceId }) logger.info('Set organization as active', { organizationId: referenceId }) } catch (error) { logger.warn('Failed to set organization as active, proceeding with upgrade', { organizationId: referenceId, error: getErrorMessage(error, 'Unknown error'), }) } } else if (orgsData.isMemberOfAnyOrg) { throw new Error( 'You are already a member of an organization. Please leave it or ask an admin to upgrade.' ) } else { logger.info('Will create organization after payment succeeds', { userId }) } } catch (error) { logger.error('Failed to prepare for team plan upgrade', error) throw error instanceof Error ? error : new Error('Failed to prepare team workspace. Please try again or contact support.') } } const currentUrl = `${window.location.origin}${window.location.pathname}` const successUrlObj = new URL(window.location.href) successUrlObj.searchParams.set('upgraded', 'true') const successUrl = successUrlObj.toString() try { const upgradeParams = { plan: planName, referenceId, successUrl, cancelUrl: currentUrl, ...(targetPlan === 'team' && { seats: CONSTANTS.INITIAL_TEAM_SEATS }), ...(annual && { annual: true }), } as const const finalParams = currentStripeSubscriptionId ? { ...upgradeParams, subscriptionId: currentStripeSubscriptionId } : upgradeParams logger.info( currentStripeSubscriptionId ? 'Upgrading existing subscription' : 'Creating new subscription', { targetPlan, planName, annual, currentStripeSubscriptionId, currentSubscriptionRowId, referenceId, } ) await betterAuthSubscription.upgrade(finalParams) if (targetPlan === 'team' && currentSubscriptionRowId && referenceId !== userId) { try { logger.info('Transferring subscription to organization after upgrade', { subscriptionId: currentSubscriptionRowId, organizationId: referenceId, }) try { await requestJson(subscriptionTransferContract, { params: { id: currentSubscriptionRowId }, body: { organizationId: referenceId }, }) logger.info('Successfully transferred subscription to organization', { subscriptionId: currentSubscriptionRowId, organizationId: referenceId, }) } catch (transferError) { logger.error('Failed to transfer subscription to organization', { subscriptionId: currentSubscriptionRowId, organizationId: referenceId, error: transferError instanceof ApiClientError ? (transferError.rawBody ?? transferError.message) : transferError instanceof Error ? transferError.message : 'Unknown error', }) } } catch (error) { logger.error('Error transferring subscription after upgrade', error) } } if (targetPlan === 'team') { try { await queryClient.invalidateQueries({ queryKey: organizationKeys.lists() }) logger.info('Refreshed organization data after team upgrade') } catch (error) { logger.warn('Failed to refresh organization data after upgrade', error) } } logger.info('Subscription upgrade completed successfully', { targetPlan, referenceId }) } catch (error) { logger.error('Failed to initiate subscription upgrade:', error) if (error instanceof Error) { logger.error('Detailed error:', { message: error.message, stack: error.stack, cause: error.cause, }) } throw new Error( `Failed to upgrade subscription: ${getErrorMessage(error, 'Unknown error')}` ) } }, [session?.user?.id, betterAuthSubscription, queryClient] ) return { handleUpgrade } }