import { Controller, Get, Post, Param, Query, Request, HttpCode, HttpStatus } from "@nestjs/common"; import { InvoiceRetrievalService } from "./services/invoice-retrieval.service.js"; import { WhmcsPaymentService } from "@bff/integrations/whmcs/services/whmcs-payment.service.js"; import { WhmcsSsoService } from "@bff/integrations/whmcs/services/whmcs-sso.service.js"; import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js"; import { createZodDto, ZodResponse } from "nestjs-zod"; import type { RequestWithUser } from "@bff/modules/auth/auth.types.js"; import type { Invoice, InvoiceList, InvoiceSsoLink } from "@customer-portal/domain/billing"; import { invoiceIdParamSchema, invoiceListQuerySchema, invoiceListSchema, invoiceSchema, invoiceSsoLinkSchema, invoiceSsoQuerySchema, } from "@customer-portal/domain/billing"; import type { PaymentMethodList } from "@customer-portal/domain/payments"; import { paymentMethodListSchema } from "@customer-portal/domain/payments"; class InvoiceListQueryDto extends createZodDto(invoiceListQuerySchema) {} class InvoiceIdParamDto extends createZodDto(invoiceIdParamSchema) {} class InvoiceListDto extends createZodDto(invoiceListSchema) {} class InvoiceDto extends createZodDto(invoiceSchema) {} class InvoiceSsoLinkDto extends createZodDto(invoiceSsoLinkSchema) {} class InvoiceSsoQueryDto extends createZodDto(invoiceSsoQuerySchema) {} class PaymentMethodListDto extends createZodDto(paymentMethodListSchema) {} /** * Billing Controller * * All request validation is handled by Zod schemas via global ZodValidationPipe. * Business logic is delegated to service layer. */ @Controller("invoices") export class BillingController { constructor( private readonly invoicesService: InvoiceRetrievalService, private readonly whmcsPaymentService: WhmcsPaymentService, private readonly whmcsSsoService: WhmcsSsoService, private readonly mappingsService: MappingsService ) {} @Get() @ZodResponse({ description: "List invoices", type: InvoiceListDto }) async getInvoices( @Request() req: RequestWithUser, @Query() query: InvoiceListQueryDto ): Promise { return this.invoicesService.getInvoices(req.user.id, query); } @Get("payment-methods") @ZodResponse({ description: "List payment methods", type: PaymentMethodListDto }) async getPaymentMethods(@Request() req: RequestWithUser): Promise { const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(req.user.id); return this.whmcsPaymentService.getPaymentMethods(whmcsClientId, req.user.id); } @Post("payment-methods/refresh") @HttpCode(HttpStatus.OK) @ZodResponse({ description: "Refresh payment methods", type: PaymentMethodListDto }) async refreshPaymentMethods(@Request() req: RequestWithUser): Promise { // Invalidate cache first await this.whmcsPaymentService.invalidatePaymentMethodsCache(req.user.id); // Return fresh payment methods const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(req.user.id); return this.whmcsPaymentService.getPaymentMethods(whmcsClientId, req.user.id); } @Get(":id") @ZodResponse({ description: "Get invoice by id", type: InvoiceDto }) async getInvoiceById( @Request() req: RequestWithUser, @Param() params: InvoiceIdParamDto ): Promise { return this.invoicesService.getInvoiceById(req.user.id, params.id); } @Post(":id/sso-link") @HttpCode(HttpStatus.OK) @ZodResponse({ description: "Create invoice SSO link", type: InvoiceSsoLinkDto }) async createSsoLink( @Request() req: RequestWithUser, @Param() params: InvoiceIdParamDto, @Query() query: InvoiceSsoQueryDto ): Promise { const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(req.user.id); const ssoUrl = await this.whmcsSsoService.whmcsSsoForInvoice( whmcsClientId, params.id, query.target ); return { url: ssoUrl, expiresAt: new Date(Date.now() + 60000).toISOString(), // 60 seconds per WHMCS spec }; } }