import {
createStripeClient,
createCheckoutSession,
createPortalSession,
stripeWebhookHandler,
} from '@latimer-woods-tech/stripe';
const stripe = createStripeClient(env.STRIPE_SECRET_KEY);
// Subscription checkout (monthly, annual, or one-time)
app.post('/api/checkout', authMiddleware, async (c) => {
const { priceId } = await c.req.json<{ priceId: string }>();
const user = c.get('user');
const url = await createCheckoutSession({
priceId,
customerId: user.stripeCustomerId,
successUrl: `${env.APP_URL}/checkout/success`,
cancelUrl: `${env.APP_URL}/pricing`,
stripeClient: stripe,
idempotencyKey: `checkout_${user.id}_${priceId}`,
paymentMethodTypes: ['card'],
metadata: { userId: user.id },
});
return c.redirect(url);
});
// Customer Portal — self-service cancel / upgrade / payment details
app.post('/api/billing/portal', authMiddleware, async (c) => {
const user = c.get('user');
const url = await createPortalSession({
customerId: user.stripeCustomerId,
returnUrl: `${env.APP_URL}/account`,
stripeClient: stripe,
});
return c.redirect(url);
});
// Webhook handler
app.post('/webhooks/stripe', stripeWebhookHandler({
webhookSecret: env.STRIPE_WEBHOOK_SECRET,
stripeClient: stripe,
handlers: {
created: async (status) => {
await db.insert(subscriptions).values({
customerId: status.customerId,
status: status.status,
tier: status.tier,
currentPeriodEnd: status.currentPeriodEnd,
});
},
canceled: async (status) => {
await db.delete(subscriptions)
.where(eq(subscriptions.customerId, status.customerId));
},
past_due: async (status) => {
logger.warn('Subscription payment failed', { customerId: status.customerId });
},
},
}));