|
import { getAuth } from 'firebase-admin/auth'; |
|
import { getFirestore } from 'firebase-admin/firestore'; |
|
import * as logger from 'firebase-functions/logger'; |
|
import { onRequest, HttpsError, onCall } from 'firebase-functions/v2/https'; |
|
import Stripe from 'stripe'; |
|
import type { CreateCheckoutSessionRequest, ManageSubscriptionRequest } from '../types/subscription'; |
|
import { |
|
createCheckoutSession, |
|
createPortalSession, |
|
getCustomerSubscription, |
|
getOrCreateStripeCustomer, |
|
} from '../services/stripe'; |
|
|
|
export const getSubscriptionPlans = (proMonthlyId: string, proYearlyId: string) => { |
|
return { |
|
PRO_MONTHLY: { |
|
id: proMonthlyId, |
|
name: 'Pro Plan', |
|
description: 'This could be your pro plan description.', |
|
price: 9.99, |
|
interval: 'month', |
|
features: [ |
|
'Advanced Analytics Dashboard', |
|
'AI-Powered Insights', |
|
'Performance Trend Tracking', |
|
'Unlimited Session Analysis', |
|
], |
|
}, |
|
PRO_YEARLY: { |
|
id: proYearlyId, |
|
name: 'Pro Plan (Annual)', |
|
description: 'This could be your annual pro plan description.', |
|
price: 99, |
|
interval: 'year', |
|
features: ['All Pro Plan Features', '20% Savings vs Monthly'], |
|
}, |
|
}; |
|
}; |
|
|
|
export const TEST_SUBSCRIPTION_PLANS = getSubscriptionPlans( |
|
'price_foo1', |
|
'price_foo2', |
|
); |
|
|
|
export const LIVE_SUBSCRIPTION_PLANS = getSubscriptionPlans( |
|
'price_foo3', |
|
'price_foo4', |
|
); |
|
|
|
|
|
const db = getFirestore(); |
|
|
|
// Create a Stripe Checkout session for subscription |
|
export const createSubscriptionCheckout = onCall(async (request) => { |
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', { |
|
apiVersion: '2025-02-24.acacia', |
|
}); |
|
try { |
|
const { priceId, successUrl, cancelUrl } = request.data as CreateCheckoutSessionRequest; |
|
const auth = getAuth(); |
|
const user = await auth.getUser(request.auth?.uid || ''); |
|
|
|
if (!user.email) { |
|
throw new HttpsError('failed-precondition', 'User must have an email to create a subscription'); |
|
} |
|
|
|
// Get or create Stripe customer |
|
const customerId = await getOrCreateStripeCustomer(stripe, user.uid, user.email); |
|
|
|
// Create checkout session |
|
const checkoutUrl = await createCheckoutSession(stripe, customerId, priceId, successUrl, cancelUrl); |
|
|
|
return { url: checkoutUrl }; |
|
} catch (error: unknown) { |
|
logger.error('Error creating subscription checkout:', error); |
|
throw new HttpsError( |
|
'internal', |
|
error instanceof Error ? error.message : 'An error occurred while creating the checkout session', |
|
); |
|
} |
|
}); |
|
|
|
// Get customer's subscription details |
|
export const getSubscription = onCall(async (request, _response) => { |
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', { |
|
apiVersion: '2025-02-24.acacia', |
|
}); |
|
try { |
|
const auth = getAuth(); |
|
const user = await auth.getUser(request.auth?.uid || ''); |
|
|
|
if (!user.email) { |
|
throw new HttpsError('failed-precondition', 'User must have an email to get subscription details'); |
|
} |
|
|
|
// Get user document to check trial status |
|
const userDoc = await db.collection('users').doc(user.uid).get(); |
|
const userData = userDoc.data(); |
|
|
|
// Calculate trial information |
|
let trialInfo = undefined; |
|
if (userData?.createdAt) { |
|
const createdAt = userData.createdAt.toDate(); |
|
const trialEndDate = new Date(createdAt.getTime() + 14 * 24 * 60 * 60 * 1000); |
|
const now = new Date(); |
|
const daysRemaining = Math.max(0, Math.ceil((trialEndDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))); |
|
|
|
if (now < trialEndDate) { |
|
trialInfo = { |
|
isTrialing: true, |
|
endDate: trialEndDate.toISOString(), |
|
daysRemaining, |
|
}; |
|
} |
|
} |
|
|
|
// Get or create Stripe customer |
|
const customerId = await getOrCreateStripeCustomer(stripe, user.uid, user.email); |
|
|
|
// Get subscription details |
|
const subscription = await getCustomerSubscription(stripe, customerId); |
|
console.log('subscription', subscription); |
|
console.log('customerId', customerId); |
|
if (!subscription) { |
|
return { |
|
subscription: { |
|
id: null, |
|
status: trialInfo ? 'trialing' : 'active', |
|
plan: { |
|
id: 'free', |
|
name: 'Free Trial', |
|
description: 'Free trial period', |
|
price: 0, |
|
interval: null, |
|
}, |
|
currentPeriodEnd: trialInfo?.endDate || null, |
|
cancelAtPeriodEnd: false, |
|
trial: trialInfo, |
|
}, |
|
}; |
|
} |
|
|
|
// Add trial info to active subscription if applicable |
|
return { |
|
subscription: { |
|
...subscription, |
|
trial: trialInfo, |
|
}, |
|
}; |
|
} catch (error: unknown) { |
|
logger.error('Error getting subscription:', error); |
|
throw new HttpsError( |
|
'internal', |
|
error instanceof Error ? error.message : 'An error occurred while fetching the subscription', |
|
); |
|
} |
|
}); |
|
|
|
// Create a Stripe Customer Portal session |
|
export const createCustomerPortal = onCall(async (request) => { |
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', { |
|
apiVersion: '2025-02-24.acacia', |
|
}); |
|
try { |
|
const { returnUrl } = request.data as ManageSubscriptionRequest; |
|
const auth = getAuth(); |
|
const user = await auth.getUser(request.auth?.uid || ''); |
|
|
|
if (!user.email) { |
|
throw new HttpsError('failed-precondition', 'User must have an email to access the customer portal'); |
|
} |
|
|
|
// Get or create Stripe customer |
|
const customerId = await getOrCreateStripeCustomer(stripe, user.uid, user.email); |
|
|
|
// Create portal session |
|
const portalUrl = await createPortalSession(stripe, customerId, returnUrl); |
|
return { url: portalUrl }; |
|
} catch (error: unknown) { |
|
logger.error('Error creating customer portal session:', error); |
|
throw new HttpsError( |
|
'internal', |
|
error instanceof Error ? error.message : 'An error occurred while creating the portal session', |
|
); |
|
} |
|
}); |
|
|
|
export const getStripeProducts = onRequest((_request, response) => { |
|
response.json({ |
|
products: getSubscriptionPlans, |
|
}); |
|
}); |