import { Controller, Get, Post, Param, Body, Logger, NotFoundException, BadRequestException, } from '@nestjs/common' import { InjectRepository } from '@nestjs/typeorm' import { Repository } from 'typeorm' import { PayoutEntity } from '@/src/entities/payout.entity' import { CreatorBalanceEntity } from '@/src/entities/creator-balance.entity' import { PayoutStatus } from '@/providers/transaction.types' /** * Admin Payouts Controller * * Administrative endpoints for managing creator payout requests. * * Routes: * - GET /admin/payouts — list all payout requests * - POST /admin/payouts/:id/approve — approve a payout * - POST /admin/payouts/:id/reject — reject a payout with reason */ @Controller('admin/payouts') export class AdminPayoutsController { private readonly logger = new Logger(AdminPayoutsController.name) constructor( @InjectRepository(PayoutEntity) private readonly payoutRepository: Repository, @InjectRepository(CreatorBalanceEntity) private readonly balanceRepository: Repository, ) {} /** * GET /admin/payouts * * List all payout requests, most recent first. */ @Get() async list() { return this.payoutRepository.find({ order: { createdAt: 'DESC' }, }) } /** * POST /admin/payouts/:id/approve * * Approve a pending payout request. Moves status to PROCESSING. * The actual disbursement happens via a separate payment provider integration. */ @Post(':id/approve') async approve(@Param('id') id: string) { const payout = await this.payoutRepository.findOne({ where: { id } }) if (!payout) { throw new NotFoundException(`Payout ${id} not found`) } if (payout.status !== PayoutStatus.PENDING) { throw new BadRequestException( `Payout ${id} cannot be approved — current status: ${payout.status}`, ) } payout.status = PayoutStatus.PROCESSING payout.processedAt = new Date() payout.metadata = { ...payout.metadata, approvedAt: new Date().toISOString(), } const updated = await this.payoutRepository.save(payout) this.logger.log(`Admin approved payout ${id} for creator ${payout.creatorUserId}`) return updated } /** * POST /admin/payouts/:id/reject * * Reject a pending payout request and return funds to creator balance. */ @Post(':id/reject') async reject( @Param('id') id: string, @Body() body: { reason: string }, ) { if (!body.reason) { throw new BadRequestException('Rejection reason is required') } const payout = await this.payoutRepository.findOne({ where: { id } }) if (!payout) { throw new NotFoundException(`Payout ${id} not found`) } if (payout.status !== PayoutStatus.PENDING) { throw new BadRequestException( `Payout ${id} cannot be rejected — current status: ${payout.status}`, ) } // Return funds to creator balance const balance = await this.balanceRepository.findOne({ where: { creatorUserId: payout.creatorUserId }, }) if (balance) { balance.availableCents = Number(balance.availableCents) + Number(payout.amountCents) balance.pendingCents = Math.max(0, Number(balance.pendingCents) - Number(payout.amountCents)) await this.balanceRepository.save(balance) } payout.status = PayoutStatus.FAILED payout.failureReason = body.reason payout.processedAt = new Date() payout.metadata = { ...payout.metadata, rejectedAt: new Date().toISOString(), rejectionReason: body.reason, } const updated = await this.payoutRepository.save(payout) this.logger.log(`Admin rejected payout ${id}: ${body.reason}`) return updated } }