Package Exports
- @donotdev/functions/firebase
- @donotdev/functions/shared
- @donotdev/functions/supabase
- @donotdev/functions/vercel
Readme
@donotdev/functions
Unified functions package supporting both Firebase Cloud Functions and Vercel Functions with clean, simple architecture. Each function is one file, with shared algorithms only for complex business logic.
๐ฆ Package vs Templates
IMPORTANT: This directory contains the published @donotdev/functions NPM package, not templates.
| Location | What It Is | Purpose |
|---|---|---|
/functions/ (this directory) |
Published NPM package | Framework code you import |
/packages/cli/templates/functions-* |
CLI scaffolding templates | Example usage (.example files) |
How they relate:
@donotdev/functions (published package)
โ imported by
Your App Functions (scaffolded by CLI)
โ uses your config
Framework Implementation + Your Business LogicExample:
// apps/your-app/functions/src/billing/createCheckoutSession.ts
import { createCheckoutSession as generic } from '@donotdev/functions/firebase';
import { stripeBackConfig } from '../config/stripeBackConfig';
// Your app's function = framework + your config
export const createCheckoutSession = generic(stripeBackConfig);See Functions Package Architecture for detailed explanation.
๐ฏ Design Principles
- 1 File Per Function: Simple, maintainable functions
- Direct Implementation: Easy functions coded directly in Firebase/Vercel files
- Shared Algorithms: Complex functions use shared business logic
- One Utils Package: Centralized utilities with internal/external separation
- Framework Integration: Functions can be scaffolded into consuming apps
๐ Package Structure
functions/src/
โโโ shared/
โ โโโ utils/ # ONE utils package
โ โ โโโ internal/ # Functions-only utilities
โ โ โ โโโ firebase.ts # Firebase Admin initialization
โ โ โ โโโ errors.ts # handleError, DoNotDevError
โ โ โ โโโ auth.ts # assertAuthenticated, assertAdmin
โ โ โ โโโ validation.ts # validateStripeEnvironment
โ โ โโโ external/ # Framework utilities
โ โ โ โโโ subscription.ts # getTierFromPriceId, etc.
โ โ โ โโโ metadata.ts # createMetadata, updateMetadata
โ โ โ โโโ date.ts # toISOString
โ โ โโโ index.ts # Export everything
โ โโโ billing/ # Shared billing algorithms
โ โ โโโ createCheckout.ts # createCheckoutAlgorithm()
โ โ โโโ processPayment.ts # processPaymentAlgorithm()
โ โ โโโ webhook.ts # webhookAlgorithm()
โ โโโ oauth/ # Shared OAuth algorithms
โ โโโ exchangeToken.ts # exchangeTokenAlgorithm()
โ โโโ grantAccess.ts # grantAccessAlgorithm()
โโโ firebase/ # Firebase Cloud Functions
โ โโโ auth/ # Simple auth functions
โ โ โโโ getUserClaims.ts # Direct Firebase call
โ โ โโโ setUserClaims.ts # Direct Firebase call
โ โ โโโ removeUserClaims.ts # Direct Firebase call
โ โโโ billing/ # Complex billing functions
โ โ โโโ createCheckout.ts # Uses shared/billing/createCheckout
โ โ โโโ processPayment.ts # Uses shared/billing/processPayment
โ โ โโโ webhook.ts # Uses shared/billing/webhook
โ โโโ crud/ # Simple CRUD functions
โ โ โโโ create.ts # Direct Firestore call
โ โ โโโ get.ts # Direct Firestore call
โ โ โโโ list.ts # Direct Firestore call
โ โโโ config/
โ โโโ constants.ts # Firebase function configs
โโโ vercel/api/ # Vercel API Routes
โโโ auth/ # Simple auth endpoints
โ โโโ getUserClaims.ts # Direct Firebase call
โ โโโ setUserClaims.ts # Direct Firebase call
โ โโโ removeUserClaims.ts # Direct Firebase call
โโโ billing/ # Complex billing endpoints
โ โโโ createCheckout.ts # Uses shared/billing/createCheckout
โ โโโ processPayment.ts # Uses shared/billing/processPayment
โ โโโ webhook.ts # Uses shared/billing/webhook
โโโ crud/ # Simple CRUD endpoints
โ โโโ create.ts # Direct Firestore call
โ โโโ get.ts # Direct Firestore call
โ โโโ list.ts # Direct Firestore call
โโโ config/
โโโ constants.ts # Vercel function configs๐๏ธ Function Architecture
Simple Functions (Direct Implementation)
Example: Get User Claims
// functions/src/firebase/auth/getUserClaims.ts
import { onCall } from 'firebase-functions/v2/https';
import { getAuth } from 'firebase-admin/auth';
import { handleError } from '../../shared/utils';
import { AUTH_CONFIG } from '../config/constants';
export const getUserClaims = onCall(AUTH_CONFIG, async (request) => {
try {
const { userId } = request.data;
const user = await getAuth().getUser(userId);
return user.customClaims || {};
} catch (error) {
throw handleError(error);
}
});Complex Functions (Shared Algorithms)
Example: Create Checkout Session
// functions/src/firebase/billing/createCheckout.ts
import { onCall } from 'firebase-functions/v2/https';
import { handleError } from '../../shared/utils';
import { createCheckoutAlgorithm } from '../../shared/billing/createCheckout';
import { STRIPE_CONFIG } from '../config/constants';
export const createCheckout = onCall(STRIPE_CONFIG, async (request) => {
try {
return await createCheckoutAlgorithm(request.data, firebaseProvider);
} catch (error) {
throw handleError(error);
}
});Shared Algorithm:
// functions/src/shared/billing/createCheckout.ts
export async function createCheckoutAlgorithm(request, provider) {
// Complex billing logic that works with both Firebase and Vercel
// Uses provider interface for platform-specific operations
}๐ฏ Framework Integration
Scaffolding Functions into Apps
When creating a new app, the framework can scaffold functions based on the chosen platform:
# Create app with Firebase functions
bun create-dndev-app my-app --platform firebase
# Create app with Vercel functions
bun create-dndev-app my-app --platform vercelGenerated Structure:
my-app/
โโโ functions/ # Scaffolded from @donotdev/functions
โ โโโ src/
โ โ โโโ firebase/ # OR vercel/api/ based on choice
โ โ โ โโโ auth/
โ โ โ โ โโโ getUserClaims.ts
โ โ โ โ โโโ setUserClaims.ts
โ โ โ โโโ billing/
โ โ โ โ โโโ createCheckout.ts
โ โ โ โ โโโ webhook.ts
โ โ โ โโโ crud/
โ โ โ โโโ create.ts
โ โ โ โโโ get.ts
โ โ โโโ shared/ # Copied shared utilities
โ โ โโโ utils/
โ โ โโโ billing/
โ โ โโโ oauth/
โ โโโ package.json
โโโ src/
โโโ components/
โโโ CheckoutButton.tsx # Uses scaffolded functionsFunction Customization
Developers can:
- Use as-is: Functions work out of the box
- Customize: Modify scaffolded functions for specific needs
- Remove: Delete unused functions
- Replace: Implement custom business logic
Example Customization:
// my-app/functions/src/firebase/auth/getUserClaims.ts
// Developer can modify this scaffolded function
export const getUserClaims = onCall(AUTH_CONFIG, async (request) => {
try {
const { userId } = request.data;
const user = await getAuth().getUser(userId);
// Custom business logic
const customClaims = user.customClaims || {};
const filteredClaims = filterSensitiveClaims(customClaims);
return filteredClaims;
} catch (error) {
throw handleError(error);
}
});๐ Quick Start
1. Framework Development
# Install dependencies
bun install
# Build all platforms
bun run build2. Environment Variables
Firebase Functions:
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_API_VERSION=2025-08-27.basil # REQUIRED - No fallbackVercel Functions:
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_API_VERSION=2025-08-27.basil # REQUIRED - No fallback
FIREBASE_ADMIN_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-...@your-project.iam.gserviceaccount.com3. Development
# Firebase development
bun run dev:firebase
# Vercel development
bun run dev:vercel4. Deployment
# Deploy to Firebase
bun run deploy:firebase
# Deploy to Vercel
bun run deploy:vercel๐ Authentication Functions
Firebase Cloud Functions
Create Checkout Session
// Called from client with Firebase Auth token
// Direct Stripe integration - no Firebase Functions needed
import { loadStripe } from '@stripe/stripe-js';
const stripe = await loadStripe(process.env.VITE_STRIPE_PUBLISHABLE_KEY);
const { error } = await stripe.redirectToCheckout({
priceId: 'price_pro_monthly',
userId: user.uid,
userEmail: user.email,
metadata: { plan: 'pro' },
allowPromotionCodes: true,
});
// Redirect to Stripe Checkout
window.location.href = result.data.sessionUrl;Stripe Webhook
- URL:
https://your-project.cloudfunctions.net/stripeWebhook - Events:
customer.subscription.*,invoice.payment.*,checkout.session.completed - Purpose: Updates Firebase custom claims with subscription data
Refresh Subscription Status
const refreshSubscription = httpsCallable(
functions,
'refreshSubscriptionStatus'
);
const result = await refreshSubscription({
userId: user.uid,
});
console.log(result.data.subscription); // Updated subscription dataVercel API Routes
Create Checkout Session
// POST /api/auth/create-checkout-session
const response = await fetch('/api/auth/create-checkout-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${await user.getIdToken()}`,
},
body: JSON.stringify({
priceId: 'price_pro_monthly',
userId: user.uid,
userEmail: user.email,
metadata: { plan: 'pro' },
}),
});
const { sessionUrl } = await response.json();
window.location.href = sessionUrl;Stripe Webhook
- URL:
https://your-domain.vercel.app/api/auth/stripe-webhook - Events: Same as Firebase version
- Purpose: Updates Firebase custom claims with subscription data
Refresh Subscription Status
// POST /api/auth/refresh-subscription
const response = await fetch('/api/auth/refresh-subscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${await user.getIdToken()}`,
},
body: JSON.stringify({
userId: user.uid,
}),
});
const { subscription } = await response.json();๐ฏ Subscription Management
How It Works
Purchase Flow:
- User clicks subscribe button
- Client calls Stripe directly
- User completes payment on Stripe
- Stripe webhook updates Firebase custom claims
Subscription Data Storage:
// Stored in Firebase Auth custom claims { subscription: { tier: 'pro' | 'ai' | 'free', subscriptionId: 'sub_1234...', customerId: 'cus_1234...', status: 'active' | 'canceled' | ..., subscriptionEnd: 1640995200000, // Unix timestamp cancelAtPeriodEnd: false, updatedAt: 1640995200000, } }
Client-Side Access:
import { useSubscription } from '@donotdev/auth'; const { subscription, loading } = useSubscription(); if (subscription?.tier === 'pro') { // Show pro features }
Tier Mapping
Configure your Stripe price IDs in src/shared/utils/index.ts:
const tierMapping: Record<string, SubscriptionTier> = {
price_pro_monthly: 'pro',
price_pro_yearly: 'pro',
price_ai_monthly: 'ai',
price_ai_yearly: 'ai',
};๐ง Configuration
Firebase Functions
Set Environment Variables:
firebase functions:config:set stripe.secret_key="sk_test_..." firebase functions:config:set stripe.webhook_secret="whsec_..."
Deploy:
bun run deploy:firebase
Configure Stripe Webhook:
- URL:
https://your-project.cloudfunctions.net/stripeWebhook - Events: Select all
customer.subscription.*andinvoice.payment.*events
- URL:
Vercel Functions
Set Environment Variables in Vercel dashboard:
STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETFIREBASE_ADMIN_PRIVATE_KEYFIREBASE_PROJECT_IDFIREBASE_CLIENT_EMAIL
Deploy:
bun run deploy:vercel
Configure Stripe Webhook:
- URL:
https://your-domain.vercel.app/api/auth/stripe-webhook - Events: Same as Firebase version
- URL:
๐งช Testing
# Run all tests
bun test
# Test specific platform
bun run test:firebase
bun run test:vercel
# Type checking
bun run typecheckTesting Webhooks Locally
Firebase Emulator
# Terminal 1: Start emulator
bun run dev:firebase
# Terminal 2: Forward webhooks
stripe listen --forward-to localhost:5001/your-project/us-central1/stripeWebhookVercel
# Terminal 1: Start Vercel dev
bun run dev:vercel
# Terminal 2: Forward webhooks
stripe listen --forward-to localhost:3000/api/auth/stripe-webhook๐ Usage Examples
Client-Side Integration
SPA (Firebase Functions)
import { getFunctions, httpsCallable } from 'firebase/functions';
import { useAuth } from '@donotdev/auth';
function CheckoutButton({ priceId }: { priceId: string }) {
const { user } = useAuth();
const functions = getFunctions();
const handleCheckout = async () => {
// Direct Stripe integration
const stripe = await loadStripe(process.env.VITE_STRIPE_PUBLISHABLE_KEY);
const { error } = await stripe.redirectToCheckout({
priceId,
userId: user.uid,
userEmail: user.email,
});
window.location.href = result.data.sessionUrl;
};
return (
<button onClick={handleCheckout}>
Subscribe
</button>
);
}Next.js (Vercel Functions)
import { useAuth } from '@donotdev/auth';
function CheckoutButton({ priceId }: { priceId: string }) {
const { user } = useAuth();
const handleCheckout = async () => {
const response = await fetch('/api/auth/create-checkout-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await user.getIdToken()}`,
},
body: JSON.stringify({
priceId,
userId: user.uid,
userEmail: user.email,
}),
});
const { sessionUrl } = await response.json();
window.location.href = sessionUrl;
};
return (
<button onClick={handleCheckout}>
Subscribe
</button>
);
}๐จ Security Notes
Firebase Functions
- Uses Firebase Admin SDK for token verification
- Automatic user authentication through callable functions
- Environment variables managed through Firebase config
Vercel Functions
- Uses Firebase Admin SDK with service account credentials
- Proper ID token verification using
auth.verifyIdToken() - Service account credentials managed through Vercel environment variables
- All authentication is production-ready and secure
๐ API Reference
Shared Types
SubscriptionTier:'free' | 'pro' | 'ai'SubscriptionStatus: Stripe subscription statusesSubscriptionClaims: Firebase custom claims structureCreateCheckoutSessionRequest: Checkout session parametersRefreshSubscriptionRequest: Subscription refresh parameters
Shared Utilities
updateUserSubscription(): Updates Firebase custom claimscancelUserSubscription(): Resets user to free tiergetUserSubscription(): Gets user's current subscriptiongetTierFromPriceId(): Maps Stripe price to tierassertAuthenticated(): Validates user authenticationDoNotDevError: Custom error class
๐ฏ Best Practices
Function Design:
- Keep simple functions simple (direct implementation)
- Use shared algorithms only for complex business logic
- One file per function for maintainability
Utils Organization:
- Internal utils: Functions-only utilities
- External utils: Framework-wide utilities
- Clear separation of concerns
Error Handling:
- Use
handleError()for consistent error responses - Use
DoNotDevErrorfor custom error types
- Use
Environment Variables:
- Always use environment variables for secrets
- Platform-specific configuration in constants files
Security:
- Verify Firebase tokens properly in production
- Use webhook secrets for security
- Handle authentication consistently
Testing:
- Test both platforms with the same business logic
- Test shared algorithms independently
Scaffolding:
- Functions should work out of the box when scaffolded
- Allow easy customization and removal
- Maintain clear interfaces for provider abstraction
๐ง Troubleshooting
Common Issues
"Missing Firebase Admin SDK":
- Ensure Firebase is initialized in shared utilities
- Check environment variables
"Webhook signature verification failed":
- Verify webhook secret matches Stripe dashboard
- Check raw body handling
"Permission denied":
- Verify Firebase Auth token is valid
- Check user has correct permissions
"Subscription not updating":
- Check webhook URL is correctly configured
- Verify Firebase custom claims are being set
Debug Mode
Enable debug logging:
# Firebase
export DEBUG=firebase-functions:*
# Vercel
export DEBUG=1๐ License
All rights reserved. The DoNotDev framework and its premium features are the exclusive property of Ambroise Park Consulting.
ยฉ Ambroise Park Consulting โ 2025