managarten/services/mana-core-auth/src/credits/credits.controller.ts
Till JS 17df7b32f5 feat(auth): add Gilden (guilds) shared Mana pool system
Replace removed B2B org credit system with consumer-friendly shared
Mana pools. Members spend directly from a guild pool managed by the
Gildenmeister (owner). Supports funding from personal balance,
per-member spending limits, and credit source routing.

New endpoints: /gilden/* (guild CRUD) and /credits/guild/* (pool ops).
POST /credits/use now accepts optional creditSource for guild routing.
Delete broken b2b-journey E2E tests that tested phantom endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:38:19 +01:00

108 lines
4.1 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 { PurchaseCreditsDto } from './dto/purchase-credits.dto';
import { CreatePaymentLinkDto } from './dto/create-payment-link.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')
@ApiOperation({ summary: 'Use credits (personal or guild pool)' })
@ApiResponse({ status: 200, description: 'Credits used successfully' })
async useCredits(@CurrentUser() user: CurrentUserData, @Body() useCreditsDto: UseCreditsDto) {
return this.creditsService.useCreditsWithSource(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);
}
@Post('payment-link')
@ApiOperation({ summary: 'Create payment link for credit purchase' })
@ApiResponse({
status: 201,
description: 'Returns Stripe Checkout URL for payment',
schema: {
properties: {
url: { type: 'string', description: 'Stripe Checkout URL' },
purchaseId: { type: 'string', description: 'Purchase ID for tracking' },
expiresAt: { type: 'string', format: 'date-time', description: 'Link expiration time' },
package: {
type: 'object',
properties: {
name: { type: 'string' },
credits: { type: 'number' },
priceEuroCents: { type: 'number' },
},
},
},
},
})
@ApiResponse({ status: 404, description: 'Package not found' })
async createPaymentLink(@CurrentUser() user: CurrentUserData, @Body() dto: CreatePaymentLinkDto) {
return this.creditsService.createPaymentLink(user.userId, dto.packageId, {
successUrl: dto.successUrl,
cancelUrl: dto.cancelUrl,
roomId: dto.roomId,
});
}
}