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

175 lines
5.2 KiB
TypeScript
Raw Normal View History

2025-08-22 17:02:49 +09:00
import {
Controller,
Get,
Post,
Param,
Query,
Request,
ParseIntPipe,
HttpCode,
HttpStatus,
BadRequestException,
UsePipes,
2025-08-22 17:02:49 +09:00
} from "@nestjs/common";
import { InvoicesOrchestratorService } from "./services/invoices-orchestrator.service";
import { WhmcsService } from "@bff/integrations/whmcs/whmcs.service";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service";
import { ZodValidationPipe } from "@bff/core/validation";
2025-08-28 16:57:57 +09:00
import type {
2025-08-22 17:02:49 +09:00
Invoice,
InvoiceList,
InvoiceSsoLink,
Subscription,
PaymentMethodList,
PaymentGatewayList,
InvoicePaymentLink,
InvoiceListQuery,
} from "@customer-portal/domain/billing";
import { invoiceListQuerySchema } from "@customer-portal/domain/billing";
2025-08-22 17:02:49 +09:00
interface AuthenticatedRequest {
user: { id: string };
}
@Controller("invoices")
export class InvoicesController {
constructor(
private readonly invoicesService: InvoicesOrchestratorService,
private readonly whmcsService: WhmcsService,
private readonly mappingsService: MappingsService
) {}
@Get()
@UsePipes(new ZodValidationPipe(invoiceListQuerySchema))
async getInvoices(
2025-08-22 17:02:49 +09:00
@Request() req: AuthenticatedRequest,
@Query() query: InvoiceListQuery
): Promise<InvoiceList> {
return this.invoicesService.getInvoices(req.user.id, query);
}
@Get("payment-methods")
async getPaymentMethods(@Request() req: AuthenticatedRequest): Promise<PaymentMethodList> {
const mapping = await this.mappingsService.findByUserId(req.user.id);
if (!mapping?.whmcsClientId) {
throw new Error("WHMCS client mapping not found");
}
return this.whmcsService.getPaymentMethods(mapping.whmcsClientId, req.user.id);
}
@Get("payment-gateways")
async getPaymentGateways(): Promise<PaymentGatewayList> {
return this.whmcsService.getPaymentGateways();
}
@Post("payment-methods/refresh")
@HttpCode(HttpStatus.OK)
async refreshPaymentMethods(@Request() req: AuthenticatedRequest): Promise<PaymentMethodList> {
// Invalidate cache first
await this.whmcsService.invalidatePaymentMethodsCache(req.user.id);
// Return fresh payment methods
const mapping = await this.mappingsService.findByUserId(req.user.id);
if (!mapping?.whmcsClientId) {
throw new Error("WHMCS client mapping not found");
}
return this.whmcsService.getPaymentMethods(mapping.whmcsClientId, req.user.id);
}
2025-08-22 17:02:49 +09:00
@Get(":id")
async getInvoiceById(
2025-08-22 17:02:49 +09:00
@Request() req: AuthenticatedRequest,
@Param("id", ParseIntPipe) invoiceId: number
): Promise<Invoice> {
if (invoiceId <= 0) {
2025-08-22 17:02:49 +09:00
throw new BadRequestException("Invoice ID must be a positive number");
}
2025-08-22 17:02:49 +09:00
return this.invoicesService.getInvoiceById(req.user.id, invoiceId);
}
2025-08-22 17:02:49 +09:00
@Get(":id/subscriptions")
getInvoiceSubscriptions(
2025-08-22 17:02:49 +09:00
@Request() req: AuthenticatedRequest,
@Param("id", ParseIntPipe) invoiceId: number
): Subscription[] {
if (invoiceId <= 0) {
2025-08-22 17:02:49 +09:00
throw new BadRequestException("Invoice ID must be a positive number");
}
2025-08-22 17:02:49 +09:00
// This functionality has been moved to WHMCS directly
// For now, return empty array as subscriptions are managed in WHMCS
return [];
}
2025-08-22 17:02:49 +09:00
@Post(":id/sso-link")
@HttpCode(HttpStatus.OK)
async createSsoLink(
2025-08-22 17:02:49 +09:00
@Request() req: AuthenticatedRequest,
@Param("id", ParseIntPipe) invoiceId: number,
@Query("target") target?: "view" | "download" | "pay"
): Promise<InvoiceSsoLink> {
if (invoiceId <= 0) {
2025-08-22 17:02:49 +09:00
throw new BadRequestException("Invoice ID must be a positive number");
}
// Validate target parameter
2025-08-22 17:02:49 +09:00
if (target && !["view", "download", "pay"].includes(target)) {
throw new BadRequestException('Target must be "view", "download", or "pay"');
}
2025-08-22 17:02:49 +09:00
const mapping = await this.mappingsService.findByUserId(req.user.id);
if (!mapping?.whmcsClientId) {
throw new Error("WHMCS client mapping not found");
}
const ssoUrl = await this.whmcsService.whmcsSsoForInvoice(
mapping.whmcsClientId,
invoiceId,
target || "view"
);
return {
url: ssoUrl,
expiresAt: new Date(Date.now() + 60000).toISOString(), // 60 seconds per WHMCS spec
};
}
2025-08-22 17:02:49 +09:00
@Post(":id/payment-link")
@HttpCode(HttpStatus.OK)
async createPaymentLink(
2025-08-22 17:02:49 +09:00
@Request() req: AuthenticatedRequest,
@Param("id", ParseIntPipe) invoiceId: number,
@Query("paymentMethodId") paymentMethodId?: string,
@Query("gatewayName") gatewayName?: string
): Promise<InvoicePaymentLink> {
if (invoiceId <= 0) {
2025-08-22 17:02:49 +09:00
throw new BadRequestException("Invoice ID must be a positive number");
}
const paymentMethodIdNum = paymentMethodId ? parseInt(paymentMethodId, 10) : undefined;
if (paymentMethodId && (isNaN(paymentMethodIdNum!) || paymentMethodIdNum! <= 0)) {
2025-08-22 17:02:49 +09:00
throw new BadRequestException("Payment method ID must be a positive number");
}
2025-08-22 17:02:49 +09:00
const mapping = await this.mappingsService.findByUserId(req.user.id);
if (!mapping?.whmcsClientId) {
throw new Error("WHMCS client mapping not found");
}
const ssoResult = await this.whmcsService.createPaymentSsoToken(
mapping.whmcsClientId,
2025-08-22 17:02:49 +09:00
invoiceId,
paymentMethodIdNum,
gatewayName || "stripe"
);
return {
url: ssoResult.url,
expiresAt: ssoResult.expiresAt,
gatewayName: gatewayName || "stripe",
};
}
}