import { Controller, Post, Get, Body, Param, UseGuards, Request, RawBodyRequest, Req, Headers, HttpStatus, HttpException, Logger, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/auth.guard'; import { PaymentsService } from './payments.service'; import { StripeService } from './services/stripe.service'; import { WebhookService } from './services/webhook.service'; import { CreateCheckoutSessionDto } from './dto/create-checkout-session.dto'; import { CreatePortalSessionDto } from './dto/create-portal-session.dto'; import { Plan } from '@prisma/client'; @ApiTags('payments') @Controller('payments') export class PaymentsController { private readonly logger = new Logger(PaymentsController.name); constructor( private readonly paymentsService: PaymentsService, private readonly stripeService: StripeService, private readonly webhookService: WebhookService, ) {} @Post('checkout') @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOperation({ summary: 'Create Stripe checkout session' }) @ApiResponse({ status: 201, description: 'Checkout session created successfully' }) async createCheckoutSession( @Request() req: any, @Body() createCheckoutSessionDto: CreateCheckoutSessionDto, ) { try { const userId = req.user.id; const session = await this.stripeService.createCheckoutSession( userId, createCheckoutSessionDto.plan, createCheckoutSessionDto.successUrl, createCheckoutSessionDto.cancelUrl, ); return { sessionId: session.id, url: session.url, }; } catch (error) { this.logger.error('Failed to create checkout session:', error); throw new HttpException( 'Failed to create checkout session', HttpStatus.INTERNAL_SERVER_ERROR, ); } } @Post('portal') @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOperation({ summary: 'Create Stripe customer portal session' }) @ApiResponse({ status: 201, description: 'Portal session created successfully' }) async createPortalSession( @Request() req: any, @Body() createPortalSessionDto: CreatePortalSessionDto, ) { try { const userId = req.user.id; const session = await this.stripeService.createPortalSession( userId, createPortalSessionDto.returnUrl, ); return { url: session.url, }; } catch (error) { this.logger.error('Failed to create portal session:', error); throw new HttpException( 'Failed to create portal session', HttpStatus.INTERNAL_SERVER_ERROR, ); } } @Get('subscription') @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOperation({ summary: 'Get user subscription details' }) @ApiResponse({ status: 200, description: 'Subscription details retrieved successfully' }) async getSubscription(@Request() req: any) { try { const userId = req.user.id; const subscription = await this.paymentsService.getUserSubscription(userId); return subscription; } catch (error) { this.logger.error('Failed to get subscription:', error); throw new HttpException( 'Failed to get subscription details', HttpStatus.INTERNAL_SERVER_ERROR, ); } } @Get('plans') @ApiOperation({ summary: 'Get available subscription plans' }) @ApiResponse({ status: 200, description: 'Plans retrieved successfully' }) async getPlans() { return { plans: [ { id: Plan.BASIC, name: 'Basic', price: 0, currency: 'usd', interval: 'month', features: [ '50 images per month', 'AI-powered naming', 'Keyword enhancement', 'ZIP download', ], quotaLimit: 50, }, { id: Plan.PRO, name: 'Pro', price: 900, // $9.00 in cents currency: 'usd', interval: 'month', features: [ '500 images per month', 'AI-powered naming', 'Keyword enhancement', 'ZIP download', 'Priority support', ], quotaLimit: 500, }, { id: Plan.MAX, name: 'Max', price: 1900, // $19.00 in cents currency: 'usd', interval: 'month', features: [ '1000 images per month', 'AI-powered naming', 'Keyword enhancement', 'ZIP download', 'Priority support', 'Advanced analytics', ], quotaLimit: 1000, }, ], }; } @Post('cancel-subscription') @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOperation({ summary: 'Cancel user subscription' }) @ApiResponse({ status: 200, description: 'Subscription cancelled successfully' }) async cancelSubscription(@Request() req: any) { try { const userId = req.user.id; await this.paymentsService.cancelSubscription(userId); return { message: 'Subscription cancelled successfully' }; } catch (error) { this.logger.error('Failed to cancel subscription:', error); throw new HttpException( 'Failed to cancel subscription', HttpStatus.INTERNAL_SERVER_ERROR, ); } } @Post('reactivate-subscription') @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOperation({ summary: 'Reactivate cancelled subscription' }) @ApiResponse({ status: 200, description: 'Subscription reactivated successfully' }) async reactivateSubscription(@Request() req: any) { try { const userId = req.user.id; await this.paymentsService.reactivateSubscription(userId); return { message: 'Subscription reactivated successfully' }; } catch (error) { this.logger.error('Failed to reactivate subscription:', error); throw new HttpException( 'Failed to reactivate subscription', HttpStatus.INTERNAL_SERVER_ERROR, ); } } @Get('payment-history') @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOperation({ summary: 'Get user payment history' }) @ApiResponse({ status: 200, description: 'Payment history retrieved successfully' }) async getPaymentHistory(@Request() req: any) { try { const userId = req.user.id; const payments = await this.paymentsService.getPaymentHistory(userId); return { payments }; } catch (error) { this.logger.error('Failed to get payment history:', error); throw new HttpException( 'Failed to get payment history', HttpStatus.INTERNAL_SERVER_ERROR, ); } } @Post('webhook') @ApiOperation({ summary: 'Handle Stripe webhooks' }) @ApiResponse({ status: 200, description: 'Webhook processed successfully' }) async handleWebhook( @Req() req: RawBodyRequest, @Headers('stripe-signature') signature: string, ) { try { await this.webhookService.handleWebhook(req.rawBody, signature); return { received: true }; } catch (error) { this.logger.error('Webhook processing failed:', error); throw new HttpException( 'Webhook processing failed', HttpStatus.BAD_REQUEST, ); } } @Post('upgrade') @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOperation({ summary: 'Upgrade subscription plan' }) @ApiResponse({ status: 200, description: 'Plan upgraded successfully' }) async upgradePlan( @Request() req: any, @Body() body: { plan: Plan; successUrl: string; cancelUrl: string }, ) { try { const userId = req.user.id; const session = await this.paymentsService.upgradePlan( userId, body.plan, body.successUrl, body.cancelUrl, ); return { sessionId: session.id, url: session.url, }; } catch (error) { this.logger.error('Failed to upgrade plan:', error); throw new HttpException( 'Failed to upgrade plan', HttpStatus.INTERNAL_SERVER_ERROR, ); } } @Post('downgrade') @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOperation({ summary: 'Downgrade subscription plan' }) @ApiResponse({ status: 200, description: 'Plan downgraded successfully' }) async downgradePlan( @Request() req: any, @Body() body: { plan: Plan }, ) { try { const userId = req.user.id; await this.paymentsService.downgradePlan(userId, body.plan); return { message: 'Plan downgraded successfully' }; } catch (error) { this.logger.error('Failed to downgrade plan:', error); throw new HttpException( 'Failed to downgrade plan', HttpStatus.INTERNAL_SERVER_ERROR, ); } } }