import { Body, Controller, Get, Param, ParseUUIDPipe, Post, Query, Res, } from "@nestjs/common"; import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger"; import type { Response } from "express"; import { CurrentUser } from "@edr/api-common"; import { type AuthUserPayload, resolveAuthUserId, } from "../../common/resolve-auth-user-id"; import { sendPdf } from "./billing.controller"; import { BillingService } from "./billing.service"; import { ConfirmOtpDto, PayInvoiceDto } from "./dto/pay-invoice.dto"; /** * Customer-facing billing endpoints. Unlike {@link BillingController} (admin, * org-wide), every route here is force-scoped to the signed-in customer's * company — they only ever see and pay their own invoices. */ @ApiTags("billing") @ApiBearerAuth() @Controller("billing") export class PortalBillingController { constructor(private readonly billingService: BillingService) {} @Get("my-invoices") @ApiOperation({ summary: "List the signed-in customer's invoices" }) findMine( @CurrentUser() user: AuthUserPayload, @Query("source") source?: string, @Query("sourceId") sourceId?: string, ) { return this.billingService.findForUser(resolveAuthUserId(user), { source, sourceId, }); } @Get("my-invoices/:id") @ApiOperation({ summary: "Get one of the customer's invoices (+ line items)" }) findMineById( @Param("id", ParseUUIDPipe) id: string, @CurrentUser() user: AuthUserPayload, ) { return this.billingService.findByIdForUser(id, resolveAuthUserId(user)); } @Get("my-invoices/:id/document") @ApiOperation({ summary: "Download one of the customer's invoice PDFs" }) async document( @Param("id", ParseUUIDPipe) id: string, @CurrentUser() user: AuthUserPayload, @Res() res: Response, ) { const { filename, buffer } = await this.billingService.documentForUser( id, resolveAuthUserId(user), ); sendPdf(res, filename, buffer); } @Get("my-invoices/:id/receipt") @ApiOperation({ summary: "Download one of the customer's payment receipt PDFs" }) async receipt( @Param("id", ParseUUIDPipe) id: string, @CurrentUser() user: AuthUserPayload, @Res() res: Response, ) { const { filename, buffer } = await this.billingService.receiptForUser( id, resolveAuthUserId(user), ); sendPdf(res, filename, buffer); } @Post("my-invoices/:id/pay") @ApiOperation({ summary: "Initiate payment for one of the customer's invoices" }) pay( @Param("id", ParseUUIDPipe) id: string, @CurrentUser() user: AuthUserPayload, @Body() dto: PayInvoiceDto, ) { return this.billingService.payInvoiceForUser(id, resolveAuthUserId(user), { method: dto.method, platform: dto.platform ?? "web", payerAccount: dto.payerAccount, returnUrl: dto.returnUrl, failureUrl: dto.failureUrl, }); } @Post("my-invoices/:id/confirm") @ApiOperation({ summary: "Confirm an OTP-debit payment (CAC Bank) for one of the customer's invoices", }) confirmOtp( @Param("id", ParseUUIDPipe) id: string, @CurrentUser() user: AuthUserPayload, @Body() dto: ConfirmOtpDto, ) { return this.billingService.confirmInvoiceOtpForUser( id, resolveAuthUserId(user), dto.otp, ); } }