Assist_Design/apps/bff/src/invoices/invoices.controller.ts

232 lines
7.8 KiB
TypeScript
Raw Normal View History

import {
Controller,
Get,
Post,
Param,
Query,
UseGuards,
Request,
ParseIntPipe,
HttpCode,
HttpStatus,
BadRequestException,
ValidationPipe,
UsePipes,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiQuery,
ApiBearerAuth,
ApiParam,
} from '@nestjs/swagger';
import { InvoicesService } from './invoices.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { Invoice, InvoiceList, InvoiceSsoLink, Subscription, PaymentMethodList, PaymentGatewayList, InvoicePaymentLink } from '@customer-portal/shared';
@ApiTags('invoices')
@Controller('invoices')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class InvoicesController {
constructor(private readonly invoicesService: InvoicesService) {}
@Get()
@ApiOperation({
summary: 'Get paginated list of user invoices',
description: 'Retrieves invoices for the authenticated user with pagination and optional status filtering'
})
@ApiQuery({ name: 'page', required: false, type: Number, description: 'Page number (default: 1)' })
@ApiQuery({ name: 'limit', required: false, type: Number, description: 'Items per page (default: 10)' })
@ApiQuery({ name: 'status', required: false, type: String, description: 'Filter by invoice status' })
@ApiResponse({
status: 200,
description: 'List of invoices with pagination',
type: Object, // Would be InvoiceList if we had proper DTO decorators
})
async getInvoices(
@Request() req: any,
@Query('page') page?: string,
@Query('limit') limit?: string,
@Query('status') status?: string,
): Promise<InvoiceList> {
// Validate and sanitize input
const pageNum = this.validatePositiveInteger(page, 1, 'page');
const limitNum = this.validatePositiveInteger(limit, 10, 'limit');
// Limit max page size for performance
if (limitNum > 100) {
throw new BadRequestException('Limit cannot exceed 100 items per page');
}
// Validate status if provided
if (status && !['Paid', 'Unpaid', 'Overdue', 'Cancelled'].includes(status)) {
throw new BadRequestException('Invalid status filter');
}
return this.invoicesService.getInvoices(
req.user.id,
{ page: pageNum, limit: limitNum, status }
);
}
@Get(':id')
@ApiOperation({
summary: 'Get invoice details by ID',
description: 'Retrieves detailed information for a specific invoice'
})
@ApiParam({ name: 'id', type: Number, description: 'Invoice ID' })
@ApiResponse({
status: 200,
description: 'Invoice details',
type: Object, // Would be Invoice if we had proper DTO decorators
})
@ApiResponse({ status: 404, description: 'Invoice not found' })
async getInvoiceById(
@Request() req: any,
@Param('id', ParseIntPipe) invoiceId: number,
): Promise<Invoice> {
if (invoiceId <= 0) {
throw new BadRequestException('Invoice ID must be a positive number');
}
return this.invoicesService.getInvoiceById(req.user.id, invoiceId);
}
@Get(':id/subscriptions')
@ApiOperation({
summary: 'Get subscriptions related to an invoice',
description: 'Retrieves all subscriptions that are referenced in the invoice items'
})
@ApiParam({ name: 'id', type: Number, description: 'Invoice ID' })
@ApiResponse({
status: 200,
description: 'List of related subscriptions',
type: [Object], // Would be Subscription[] if we had proper DTO decorators
})
@ApiResponse({ status: 404, description: 'Invoice not found' })
async getInvoiceSubscriptions(
@Request() req: any,
@Param('id', ParseIntPipe) invoiceId: number,
): Promise<Subscription[]> {
if (invoiceId <= 0) {
throw new BadRequestException('Invoice ID must be a positive number');
}
return this.invoicesService.getInvoiceSubscriptions(req.user.id, invoiceId);
}
@Post(':id/sso-link')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Create SSO link for invoice',
description: 'Generates a single sign-on link to view/pay the invoice or download PDF in WHMCS'
})
@ApiParam({ name: 'id', type: Number, description: 'Invoice ID' })
@ApiQuery({ name: 'target', required: false, enum: ['view', 'download', 'pay'], description: 'Link target: view invoice, download PDF, or go to payment page (default: view)' })
@ApiResponse({
status: 200,
description: 'SSO link created successfully',
type: Object, // Would be InvoiceSsoLink if we had proper DTO decorators
})
@ApiResponse({ status: 404, description: 'Invoice not found' })
async createSsoLink(
@Request() req: any,
@Param('id', ParseIntPipe) invoiceId: number,
@Query('target') target?: 'view' | 'download' | 'pay',
): Promise<InvoiceSsoLink> {
if (invoiceId <= 0) {
throw new BadRequestException('Invoice ID must be a positive number');
}
// Validate target parameter
if (target && !['view', 'download', 'pay'].includes(target)) {
throw new BadRequestException('Target must be "view", "download", or "pay"');
}
return this.invoicesService.createSsoLink(req.user.id, invoiceId, target || 'view');
}
@Get('payment-methods')
@ApiOperation({
summary: 'Get user payment methods',
description: 'Retrieves all saved payment methods for the authenticated user'
})
@ApiResponse({
status: 200,
description: 'List of payment methods',
type: Object, // Would be PaymentMethodList if we had proper DTO decorators
})
async getPaymentMethods(
@Request() req: any,
): Promise<PaymentMethodList> {
return this.invoicesService.getPaymentMethods(req.user.id);
}
@Get('payment-gateways')
@ApiOperation({
summary: 'Get available payment gateways',
description: 'Retrieves all active payment gateways available for payments'
})
@ApiResponse({
status: 200,
description: 'List of payment gateways',
type: Object, // Would be PaymentGatewayList if we had proper DTO decorators
})
async getPaymentGateways(): Promise<PaymentGatewayList> {
return this.invoicesService.getPaymentGateways();
}
@Post(':id/payment-link')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Create payment link for invoice with payment method',
description: 'Generates a payment link for the invoice with a specific payment method or gateway'
})
@ApiParam({ name: 'id', type: Number, description: 'Invoice ID' })
@ApiQuery({ name: 'paymentMethodId', required: false, type: Number, description: 'Payment method ID' })
@ApiQuery({ name: 'gatewayName', required: false, type: String, description: 'Payment gateway name' })
@ApiResponse({
status: 200,
description: 'Payment link created successfully',
type: Object, // Would be InvoicePaymentLink if we had proper DTO decorators
})
@ApiResponse({ status: 404, description: 'Invoice not found' })
async createPaymentLink(
@Request() req: any,
@Param('id', ParseIntPipe) invoiceId: number,
@Query('paymentMethodId') paymentMethodId?: string,
@Query('gatewayName') gatewayName?: string,
): Promise<InvoicePaymentLink> {
if (invoiceId <= 0) {
throw new BadRequestException('Invoice ID must be a positive number');
}
const paymentMethodIdNum = paymentMethodId ? parseInt(paymentMethodId, 10) : undefined;
if (paymentMethodId && (isNaN(paymentMethodIdNum!) || paymentMethodIdNum! <= 0)) {
throw new BadRequestException('Payment method ID must be a positive number');
}
return this.invoicesService.createPaymentSsoLink(
req.user.id,
invoiceId,
paymentMethodIdNum,
gatewayName
);
}
private validatePositiveInteger(value: string | undefined, defaultValue: number, fieldName: string): number {
if (!value) {
return defaultValue;
}
const parsed = parseInt(value, 10);
if (isNaN(parsed) || parsed <= 0) {
throw new BadRequestException(`${fieldName} must be a positive integer`);
}
return parsed;
}
}