managarten/services/mana-core-auth/src/subscriptions/subscriptions.controller.ts
Till-JS ae30ce3323 feat(auth): add Stripe credit purchases and subscription management
- Add StripeService for PaymentIntent creation and webhook verification
- Add credit purchase flow (POST /credits/purchase)
- Add stripe_customers table for Stripe customer mapping
- Add subscriptions schema (plans, subscriptions, invoices)
- Add SubscriptionsService with Checkout, Portal, Cancel, Reactivate
- Add subscription plans (Free: 150 Mana, Pro: €9.99, Enterprise: €49.99)
- Handle subscription and invoice webhooks
- Update roadmap with completed tasks

Credit pricing: 1 Mana = 1 Cent (no volume discounts)
2026-02-13 22:21:23 +01:00

103 lines
4 KiB
TypeScript

import { Controller, Get, Post, Body, Param, UseGuards, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { SubscriptionsService } from './subscriptions.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser } from '../common/decorators/current-user.decorator';
import type { CurrentUserData } from '../common/decorators/current-user.decorator';
import { CreateCheckoutSessionDto } from './dto/create-checkout-session.dto';
import { CreatePortalSessionDto } from './dto/create-portal-session.dto';
@ApiTags('subscriptions')
@Controller('subscriptions')
export class SubscriptionsController {
constructor(private readonly subscriptionsService: SubscriptionsService) {}
// ============================================================================
// PUBLIC ENDPOINTS
// ============================================================================
@Get('plans')
@ApiOperation({ summary: 'Get all available subscription plans' })
@ApiResponse({ status: 200, description: 'Returns list of active plans' })
async getPlans() {
return this.subscriptionsService.getPlans();
}
@Get('plans/:planId')
@ApiOperation({ summary: 'Get a specific plan' })
@ApiResponse({ status: 200, description: 'Returns plan details' })
@ApiResponse({ status: 404, description: 'Plan not found' })
async getPlan(@Param('planId') planId: string) {
return this.subscriptionsService.getPlan(planId);
}
// ============================================================================
// PROTECTED ENDPOINTS
// ============================================================================
@Get('current')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Get current subscription' })
@ApiResponse({ status: 200, description: 'Returns current subscription and plan' })
async getCurrentSubscription(@CurrentUser() user: CurrentUserData) {
return this.subscriptionsService.getCurrentSubscription(user.userId);
}
@Post('checkout')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Create Stripe checkout session for subscription' })
@ApiResponse({ status: 201, description: 'Returns checkout session URL' })
async createCheckoutSession(
@CurrentUser() user: CurrentUserData,
@Body() dto: CreateCheckoutSessionDto
) {
return this.subscriptionsService.createCheckoutSession(
user.userId,
dto.planId,
dto.billingInterval,
dto.successUrl,
dto.cancelUrl
);
}
@Post('portal')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Create Stripe Customer Portal session' })
@ApiResponse({ status: 201, description: 'Returns portal URL for billing management' })
async createPortalSession(
@CurrentUser() user: CurrentUserData,
@Body() dto: CreatePortalSessionDto
) {
return this.subscriptionsService.createPortalSession(user.userId, dto.returnUrl);
}
@Post('cancel')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Cancel subscription at period end' })
@ApiResponse({ status: 200, description: 'Subscription scheduled for cancellation' })
async cancelSubscription(@CurrentUser() user: CurrentUserData) {
return this.subscriptionsService.cancelSubscription(user.userId);
}
@Post('reactivate')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Reactivate a canceled subscription' })
@ApiResponse({ status: 200, description: 'Subscription reactivated' })
async reactivateSubscription(@CurrentUser() user: CurrentUserData) {
return this.subscriptionsService.reactivateSubscription(user.userId);
}
@Get('invoices')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Get invoice history' })
@ApiResponse({ status: 200, description: 'Returns list of invoices' })
async getInvoices(@CurrentUser() user: CurrentUserData, @Query('limit') limit?: number) {
return this.subscriptionsService.getInvoices(user.userId, limit);
}
}