Initial commit — eLegal Software monorepo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-04-26 02:42:42 -04:00
co-authored by Claude Sonnet 4.6
commit 0700d54225
160 changed files with 22771 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { eq } from 'drizzle-orm';
import { getDb, firms } from '@lawdesk/db';
import { env } from '../env';
import { getStripe, getPlanConfig, stripeIsConfigured } from '../lib/stripe';
export async function billingRoutes(app: FastifyInstance) {
app.addHook('preHandler', app.requireFirm);
// Status — what does the UI need to show? Configured at all? Current plan? Has subscription?
app.get('/api/billing/status', async (req) => {
const firmId = req.user!.firmId!;
const [firm] = await getDb().select().from(firms).where(eq(firms.id, firmId)).limit(1);
return {
configured: stripeIsConfigured(),
plan: firm?.plan ?? 'starter',
hasSubscription: !!firm?.stripeSubscriptionId,
hasCustomer: !!firm?.stripeCustomerId,
};
});
// Create a Checkout Session — returns the URL to redirect the user to.
app.post('/api/billing/checkout', async (req, reply) => {
const parsed = z
.object({ plan: z.enum(['pro', 'lifetime']) })
.safeParse(req.body);
if (!parsed.success) return reply.code(400).send({ error: 'invalid_plan' });
if (!stripeIsConfigured()) return reply.code(503).send({ error: 'stripe_not_configured' });
const firmId = req.user!.firmId!;
const userEmail = req.user!.email;
const planCfg = getPlanConfig(parsed.data.plan);
if (!planCfg) return reply.code(503).send({ error: 'plan_not_configured' });
const db = getDb();
const [firm] = await db.select().from(firms).where(eq(firms.id, firmId)).limit(1);
if (!firm) return reply.code(404).send({ error: 'firm_not_found' });
const stripe = getStripe();
// Reuse the customer if we've made one before; otherwise let Checkout create one and we'll
// capture it on the webhook.
const session = await stripe.checkout.sessions.create({
mode: planCfg.mode,
line_items: [{ price: planCfg.priceId, quantity: 1 }],
customer: firm.stripeCustomerId ?? undefined,
customer_email: firm.stripeCustomerId ? undefined : userEmail,
client_reference_id: firmId,
metadata: { firmId, plan: planCfg.planName },
subscription_data:
planCfg.mode === 'subscription' ? { metadata: { firmId, plan: planCfg.planName } } : undefined,
success_url: `${env.PUBLIC_URL}/billing/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${env.PUBLIC_URL}/billing/cancel`,
allow_promotion_codes: true,
});
return { url: session.url };
});
// Customer Portal — for managing the subscription, updating payment method, viewing invoices.
app.post('/api/billing/portal', async (req, reply) => {
if (!stripeIsConfigured()) return reply.code(503).send({ error: 'stripe_not_configured' });
const firmId = req.user!.firmId!;
const [firm] = await getDb().select().from(firms).where(eq(firms.id, firmId)).limit(1);
if (!firm?.stripeCustomerId) return reply.code(404).send({ error: 'no_customer' });
const stripe = getStripe();
const session = await stripe.billingPortal.sessions.create({
customer: firm.stripeCustomerId,
return_url: `${env.PUBLIC_URL}/app/settings`,
});
return { url: session.url };
});
}