it0/packages/services/billing-service/src/interfaces/rest/controllers/webhook.controller.ts

59 lines
2.0 KiB
TypeScript

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<string, string>) {
return this.processWebhook(PaymentProviderType.STRIPE, req, headers);
}
@Post('alipay')
@HttpCode(HttpStatus.OK)
async alipayWebhook(@Req() req: Request, @Headers() headers: Record<string, string>) {
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<string, string>) {
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<string, string>) {
return this.processWebhook(PaymentProviderType.CRYPTO, req, headers);
}
private async processWebhook(
provider: PaymentProviderType,
req: Request,
headers: Record<string, string>,
) {
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;
}
}
}