import { Controller, Post, Req, Headers, HttpCode, HttpStatus, Logger, BadRequestException, } from '@nestjs/common'; import { Request } from 'express'; import { HandlePaymentWebhookUseCase } from '../../../application/use-cases/handle-payment-webhook.use-case'; import { PaymentProviderType } from '../../../domain/ports/payment-provider.port'; @Controller('api/v1/billing/webhooks') export class WebhookController { private readonly logger = new Logger(WebhookController.name); constructor(private readonly handleWebhook: HandlePaymentWebhookUseCase) {} @Post('stripe') @HttpCode(HttpStatus.OK) async stripeWebhook(@Req() req: Request, @Headers() headers: Record) { return this.processWebhook(PaymentProviderType.STRIPE, req, headers); } @Post('alipay') @HttpCode(HttpStatus.OK) async alipayWebhook(@Req() req: Request, @Headers() headers: Record) { await this.processWebhook(PaymentProviderType.ALIPAY, req, headers); // Alipay expects plain text "success" response return 'success'; } @Post('wechat') @HttpCode(HttpStatus.OK) async wechatWebhook(@Req() req: Request, @Headers() headers: Record) { await this.processWebhook(PaymentProviderType.WECHAT_PAY, req, headers); return { code: 'SUCCESS', message: '成功' }; } @Post('crypto') @HttpCode(HttpStatus.OK) async cryptoWebhook(@Req() req: Request, @Headers() headers: Record) { return this.processWebhook(PaymentProviderType.CRYPTO, req, headers); } private async processWebhook( provider: PaymentProviderType, req: Request, headers: Record, ) { const rawBody = (req as any).rawBody as Buffer; if (!rawBody) { throw new BadRequestException('Raw body not available — ensure rawBody middleware is enabled'); } try { await this.handleWebhook.execute(provider, rawBody, headers); } catch (err) { this.logger.error(`Webhook error for ${provider}: ${err.message}`); throw err; } } }