Files
backend/src/stripe/stripe.controller.ts

204 lines
6.0 KiB
TypeScript

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<string>('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<string>('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();
}
}