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

167 lines
6.0 KiB
TypeScript
Raw Normal View History

import { Controller, Get, Post, Param, Query, Request, HttpCode, HttpStatus } from "@nestjs/common";
import { InvoicesOrchestratorService } from "./services/invoices-orchestrator.service.js";
import { WhmcsService } from "@bff/integrations/whmcs/whmcs.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";
2025-08-28 16:57:57 +09:00
import type { Invoice, InvoiceList, InvoiceSsoLink } from "@customer-portal/domain/billing";
import {
invoiceIdParamSchema,
invoiceListQuerySchema,
invoiceListSchema,
invoiceSchema,
invoiceSsoLinkSchema,
invoiceSsoQuerySchema,
invoicePaymentLinkQuerySchema,
} from "@customer-portal/domain/billing";
import type { Subscription } from "@customer-portal/domain/subscriptions";
import type {
PaymentMethodList,
PaymentGatewayList,
InvoicePaymentLink,
} from "@customer-portal/domain/payments";
import {
paymentMethodListSchema,
paymentGatewayListSchema,
invoicePaymentLinkSchema,
} 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 InvoicePaymentLinkQueryDto extends createZodDto(invoicePaymentLinkQuerySchema) {}
class PaymentMethodListDto extends createZodDto(paymentMethodListSchema) {}
class PaymentGatewayListDto extends createZodDto(paymentGatewayListSchema) {}
class InvoicePaymentLinkDto extends createZodDto(invoicePaymentLinkSchema) {}
/**
* Billing Controller
*
* All request validation is handled by Zod schemas via global ZodValidationPipe.
* Business logic is delegated to service layer.
*/
2025-08-22 17:02:49 +09:00
@Controller("invoices")
export class BillingController {
constructor(
private readonly invoicesService: InvoicesOrchestratorService,
private readonly whmcsService: WhmcsService,
private readonly mappingsService: MappingsService
) {}
@Get()
@ZodResponse({ description: "List invoices", type: InvoiceListDto })
async getInvoices(
@Request() req: RequestWithUser,
@Query() query: InvoiceListQueryDto
): Promise<InvoiceList> {
return this.invoicesService.getInvoices(req.user.id, query);
}
@Get("payment-methods")
@ZodResponse({ description: "List payment methods", type: PaymentMethodListDto })
async getPaymentMethods(@Request() req: RequestWithUser): 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")
@ZodResponse({ description: "List payment gateways", type: PaymentGatewayListDto })
async getPaymentGateways(): Promise<PaymentGatewayList> {
return this.whmcsService.getPaymentGateways();
}
@Post("payment-methods/refresh")
@HttpCode(HttpStatus.OK)
@ZodResponse({ description: "Refresh payment methods", type: PaymentMethodListDto })
async refreshPaymentMethods(@Request() req: RequestWithUser): 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")
@ZodResponse({ description: "Get invoice by id", type: InvoiceDto })
async getInvoiceById(
@Request() req: RequestWithUser,
@Param() params: InvoiceIdParamDto
): Promise<Invoice> {
return this.invoicesService.getInvoiceById(req.user.id, params.id);
}
2025-08-22 17:02:49 +09:00
@Get(":id/subscriptions")
getInvoiceSubscriptions(): Subscription[] {
// 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)
@ZodResponse({ description: "Create invoice SSO link", type: InvoiceSsoLinkDto })
async createSsoLink(
@Request() req: RequestWithUser,
@Param() params: InvoiceIdParamDto,
@Query() query: InvoiceSsoQueryDto
): Promise<InvoiceSsoLink> {
const mapping = await this.mappingsService.findByUserId(req.user.id);
if (!mapping?.whmcsClientId) {
throw new Error("WHMCS client mapping not found");
}
const parsedQuery = invoiceSsoQuerySchema.parse(query as unknown);
const ssoUrl = await this.whmcsService.whmcsSsoForInvoice(
mapping.whmcsClientId,
params.id,
parsedQuery.target
);
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)
@ZodResponse({ description: "Create invoice payment link", type: InvoicePaymentLinkDto })
async createPaymentLink(
@Request() req: RequestWithUser,
@Param() params: InvoiceIdParamDto,
@Query() query: InvoicePaymentLinkQueryDto
): Promise<InvoicePaymentLink> {
const mapping = await this.mappingsService.findByUserId(req.user.id);
if (!mapping?.whmcsClientId) {
throw new Error("WHMCS client mapping not found");
}
const parsedQuery = invoicePaymentLinkQuerySchema.parse(query as unknown);
const ssoResult = await this.whmcsService.createPaymentSsoToken(
mapping.whmcsClientId,
params.id,
parsedQuery.paymentMethodId,
parsedQuery.gatewayName
);
return {
url: ssoResult.url,
expiresAt: ssoResult.expiresAt,
gatewayName: parsedQuery.gatewayName,
};
}
}