learning_ai_common_plat/services/billing-service/src/modules/subscriptions/routes.ts
saravanakumardb1 90b9cf93d8 fix(common): configure ESLint 9 and fix lint issues
- Added @eslint/js dependency
- Updated eslint.config.js for ESLint 9 compatibility
- Added required globals (crypto, localStorage, React, etc.)
- Fixed unused imports and variables
- Disabled sort-imports temporarily
- Formatted all files with Prettier
2026-02-12 16:37:30 -08:00

109 lines
3.8 KiB
TypeScript

/**
* Subscription + payment REST endpoints.
*
* GET /subscriptions/:userId — get user subscription
* POST /subscriptions — create subscription
* PUT /subscriptions/:id — update subscription
* GET /payments/:userId — list user payments
* POST /payments — record a payment
*/
import type { FastifyInstance } from 'fastify';
import { PRODUCT_ID } from '../../lib/product-config.js';
import { BadRequestError, NotFoundError } from '../../lib/errors.js';
import * as repo from './repository.js';
import {
CreateSubscriptionSchema,
UpdateSubscriptionSchema,
CreatePaymentSchema,
type SubscriptionDoc,
type PaymentDoc,
} from './types.js';
export async function subscriptionRoutes(app: FastifyInstance) {
// Get subscription by userId
app.get('/subscriptions/:userId', async req => {
const { userId } = req.params as { userId: string };
const sub = await repo.getByUserId(userId);
if (!sub) throw new NotFoundError('Subscription not found');
return sub;
});
// Create subscription
app.post('/subscriptions', async (req, reply) => {
const parsed = CreateSubscriptionSchema.safeParse(req.body);
if (!parsed.success) {
throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
}
const input = parsed.data;
const now = new Date();
const periodEnd = new Date(now);
if (input.trialDays && input.trialDays > 0) {
periodEnd.setDate(periodEnd.getDate() + input.trialDays);
} else {
periodEnd.setMonth(periodEnd.getMonth() + 1);
}
const doc: SubscriptionDoc = {
id: `sub_${input.userId}_${Date.now()}`,
productId: PRODUCT_ID,
userId: input.userId,
plan: input.plan,
status: input.status,
currentPeriodStart: now.toISOString(),
currentPeriodEnd: periodEnd.toISOString(),
cancelAtPeriodEnd: false,
monthlyPrice: input.monthlyPrice,
tokensIncluded: input.tokensIncluded,
tokensUsed: 0,
...(input.stripeCustomerId && { stripeCustomerId: input.stripeCustomerId }),
...(input.stripeSubscriptionId && { stripeSubscriptionId: input.stripeSubscriptionId }),
createdAt: now.toISOString(),
updatedAt: now.toISOString(),
};
const created = await repo.createSubscription(doc);
reply.code(201);
return created;
});
// Update subscription by userId (looks up subscription, then updates)
app.put('/subscriptions/:userId', async req => {
const { userId } = req.params as { userId: string };
const parsed = UpdateSubscriptionSchema.safeParse(req.body);
if (!parsed.success) {
throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
}
const existing = await repo.getByUserId(userId);
if (!existing) throw new NotFoundError('Subscription not found');
const updated = await repo.updateSubscription(existing.id, userId, parsed.data);
if (!updated) throw new NotFoundError('Subscription update failed');
return updated;
});
// List payments
app.get('/payments/:userId', async req => {
const { userId } = req.params as { userId: string };
const { limit = '50' } = req.query as { limit?: string };
return { payments: await repo.getPaymentsByUser(userId, Number(limit)) };
});
// Create payment
app.post('/payments', async (req, reply) => {
const parsed = CreatePaymentSchema.safeParse(req.body);
if (!parsed.success) {
throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
}
const input = parsed.data;
const doc: PaymentDoc = {
id: `pay_${crypto.randomUUID()}`,
productId: PRODUCT_ID,
...input,
createdAt: new Date().toISOString(),
};
const created = await repo.createPayment(doc);
reply.code(201);
return created;
});
}