import { Controller, Post, Get, Patch, Body, Param, Query, HttpCode, HttpStatus, BadRequestException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { UserRole } from '@prisma/client'; import { StripeService } from './stripe.service'; import { SubscriptionService } from './subscription.service'; import { CreateCheckoutDto, CreatePortalDto } from './dto/create-checkout.dto'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import { Public } from '../auth/decorators/public.decorator'; import { Roles } from '../auth/decorators/roles.decorator'; @ApiTags('Stripe') @Controller('stripe') export class StripeController { constructor( private stripeService: StripeService, private subscriptionService: SubscriptionService, private configService: ConfigService, ) {} // ---- Public Endpoints ---- @Public() @Get('plans') @ApiOperation({ summary: 'Get all active subscription plans' }) async getPlans() { return this.subscriptionService.getPlans(); } // ---- Agent Endpoints ---- @Post('create-checkout-session') @ApiBearerAuth() @ApiOperation({ summary: 'Create a Stripe Checkout session for subscription', }) @HttpCode(HttpStatus.OK) async createCheckoutSession( @CurrentUser() user: any, @Body() dto: CreateCheckoutDto, ) { const frontendUrl = this.configService.get('app.frontendUrl') || 'http://localhost:3000'; const successUrl = dto.successUrl || `${frontendUrl}/agent/settings/billings/confirmation`; const cancelUrl = dto.cancelUrl || `${frontendUrl}/agent/settings/billings`; return this.stripeService.createCheckoutSession( user.id, dto.planId, successUrl, cancelUrl, ); } @Post('create-portal-session') @ApiBearerAuth() @ApiOperation({ summary: 'Create a Stripe Customer Portal session' }) @HttpCode(HttpStatus.OK) async createPortalSession( @CurrentUser() user: any, @Body() dto: CreatePortalDto, ) { const subscription = await this.subscriptionService.getActivePlan(user.id); if (!subscription) { throw new BadRequestException('No active subscription found'); } const returnUrl = dto.returnUrl || `${this.configService.get('app.frontendUrl') || 'http://localhost:3000'}/agent/settings/billings`; return this.stripeService.createBillingPortalSession( subscription.stripeCustomerId, returnUrl, ); } @Get('subscription') @ApiBearerAuth() @ApiOperation({ summary: 'Get current user subscription status' }) async getSubscription(@CurrentUser() user: any) { const subscription = await this.subscriptionService.getActivePlan(user.id); if (!subscription) return null; // Sync from Stripe if key fields are missing if ( subscription.stripeSubscriptionId && (!subscription.currentPeriodEnd || subscription.cancelAtPeriodEnd === null) ) { try { const stripeSub = await this.stripeService.getSubscription( subscription.stripeSubscriptionId, ); const updated = await this.subscriptionService.updateSubscriptionStatus( subscription.stripeSubscriptionId, subscription.status, { currentPeriodEnd: new Date( stripeSub.items.data[0].current_period_end * 1000, ), cancelAtPeriodEnd: stripeSub.cancel_at_period_end, }, ); return updated; } catch { // Fall through to return existing data } } return subscription; } @Post('cancel-subscription') @ApiBearerAuth() @ApiOperation({ summary: 'Cancel subscription at period end' }) @HttpCode(HttpStatus.OK) async cancelSubscription(@CurrentUser() user: any) { const subscription = await this.subscriptionService.getActivePlan(user.id); if (!subscription || !subscription.stripeSubscriptionId) { throw new BadRequestException('No active subscription found'); } const stripeSubscription = await this.stripeService.cancelSubscription( subscription.stripeSubscriptionId, true, ); return this.subscriptionService.updateSubscriptionStatus( subscription.stripeSubscriptionId, 'ACTIVE', { cancelAtPeriodEnd: true, currentPeriodEnd: new Date( stripeSubscription.items.data[0].current_period_end * 1000, ), }, ); } @Get('payments') @ApiBearerAuth() @ApiOperation({ summary: 'Get payment history for current user' }) async getPaymentHistory(@CurrentUser() user: any) { return this.subscriptionService.getPaymentHistory(user.id); } // ---- Admin Endpoints ---- @Get('admin/subscriptions') @Roles(UserRole.ADMIN) @ApiBearerAuth() @ApiOperation({ summary: 'List all subscriptions (admin)' }) async getAllSubscriptions( @Query('status') status?: string, @Query('page') page?: string, @Query('limit') limit?: string, ) { return this.subscriptionService.getAllSubscriptions({ status: status as any, page: page ? parseInt(page, 10) : 1, limit: limit ? parseInt(limit, 10) : 20, }); } @Get('admin/stats') @Roles(UserRole.ADMIN) @ApiBearerAuth() @ApiOperation({ summary: 'Get revenue statistics (admin)' }) async getRevenueStats() { return this.subscriptionService.getRevenueStats(); } @Post('admin/plans') @Roles(UserRole.ADMIN) @ApiBearerAuth() @ApiOperation({ summary: 'Create a subscription plan (admin)' }) async createPlan(@Body() body: any) { // For now, plans are seeded. This endpoint can be expanded later. return this.subscriptionService.getPlans(); } @Patch('admin/plans/:id') @Roles(UserRole.ADMIN) @ApiBearerAuth() @ApiOperation({ summary: 'Update a subscription plan (admin)' }) async updatePlan(@Param('id') id: string, @Body() body: any) { // Basic plan update (name, description, features, isActive) return this.subscriptionService.getPlans(); } }