managarten/services/mana-core-auth/src/credits/credits.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

123 lines
4.5 KiB
TypeScript

import { Controller, Get, Post, Body, UseGuards, Query, ParseIntPipe, Param } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { CreditsService } from './credits.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 { UseCreditsDto } from './dto/use-credits.dto';
import { AllocateCreditsDto } from './dto/allocate-credits.dto';
import { PurchaseCreditsDto } from './dto/purchase-credits.dto';
@ApiTags('credits')
@ApiBearerAuth('JWT-auth')
@Controller('credits')
@UseGuards(JwtAuthGuard)
export class CreditsController {
constructor(private readonly creditsService: CreditsService) {}
// ============================================================================
// PERSONAL / B2C ENDPOINTS
// ============================================================================
@Get('balance')
@ApiOperation({ summary: 'Get current credit balance' })
@ApiResponse({ status: 200, description: 'Returns user credit balance' })
async getBalance(@CurrentUser() user: CurrentUserData) {
return this.creditsService.getBalance(user.userId);
}
@Post('use')
async useCredits(@CurrentUser() user: CurrentUserData, @Body() useCreditsDto: UseCreditsDto) {
return this.creditsService.useCredits(user.userId, useCreditsDto);
}
@Get('transactions')
async getTransactionHistory(
@CurrentUser() user: CurrentUserData,
@Query('limit', new ParseIntPipe({ optional: true })) limit?: number,
@Query('offset', new ParseIntPipe({ optional: true })) offset?: number
) {
return this.creditsService.getTransactionHistory(user.userId, limit, offset);
}
@Get('purchases')
async getPurchaseHistory(@CurrentUser() user: CurrentUserData) {
return this.creditsService.getPurchaseHistory(user.userId);
}
@Get('packages')
@ApiOperation({ summary: 'Get available credit packages' })
@ApiResponse({ status: 200, description: 'Returns list of active credit packages' })
async getPackages() {
return this.creditsService.getPackages();
}
@Post('purchase')
@ApiOperation({ summary: 'Initiate credit purchase' })
@ApiResponse({
status: 201,
description: 'Returns Stripe PaymentIntent client secret for frontend payment',
})
@ApiResponse({ status: 404, description: 'Package not found' })
async initiatePurchase(@CurrentUser() user: CurrentUserData, @Body() dto: PurchaseCreditsDto) {
return this.creditsService.initiatePurchase(user.userId, dto.packageId);
}
@Get('purchase/:purchaseId')
@ApiOperation({ summary: 'Get purchase status' })
@ApiResponse({ status: 200, description: 'Returns purchase details and status' })
@ApiResponse({ status: 404, description: 'Purchase not found' })
async getPurchaseStatus(
@CurrentUser() user: CurrentUserData,
@Param('purchaseId') purchaseId: string
) {
return this.creditsService.getPurchaseStatus(user.userId, purchaseId);
}
// ============================================================================
// ORGANIZATION / B2B ENDPOINTS
// ============================================================================
/**
* Allocate credits from organization to employee
* Only organization owners can allocate credits
*/
@Post('organization/allocate')
async allocateCredits(
@CurrentUser() user: CurrentUserData,
@Body() allocateDto: AllocateCreditsDto
) {
return this.creditsService.allocateCredits(user.userId, allocateDto);
}
/**
* Get organization credit balance and allocation stats
*/
@Get('organization/:organizationId/balance')
async getOrganizationBalance(@Param('organizationId') organizationId: string) {
return this.creditsService.getOrganizationBalance(organizationId);
}
/**
* Get employee's credit balance within an organization context
*/
@Get('organization/:organizationId/employee/:employeeId/balance')
async getEmployeeBalance(
@Param('organizationId') organizationId: string,
@Param('employeeId') employeeId: string
) {
return this.creditsService.getEmployeeCreditBalance(employeeId, organizationId);
}
/**
* Deduct credits with organization tracking (for B2B usage)
*/
@Post('organization/:organizationId/use')
async deductCreditsWithOrgTracking(
@CurrentUser() user: CurrentUserData,
@Param('organizationId') organizationId: string,
@Body() useCreditsDto: UseCreditsDto
) {
return this.creditsService.deductCredits(user.userId, useCreditsDto, organizationId);
}
}