我正在 Nest.js 应用程序中设置 Stripe Webhook,但在访问原始请求正文 (req.rawBody) 时遇到问题。 Stripe 需要此原始正文来进行 Webhook 签名验证。尽管按照 Nest.js 文档 (https://docs.nestjs.com/faq/raw-body) 配置 rawBody,req.rawBody 返回未定义,导致 Stripe 出现“Webhook 签名验证失败”错误。
这是我的 webhook 处理程序的片段:
@Controller('panier')
export class PanierController {
private stripe: Stripe;
constructor(private config: ConfigService) {
this.stripe = new Stripe(this.config.get('STRIPE_SECRET'));
}
@Post('webhook')
async incomingEvents(@Headers('stripe-signature') signature: string, @Req() req: RawBodyRequest<Request>) {
const webhookSecret = this.config.get('STRIPE_WEBHOOK_SECRET');
if (!signature) {
throw new BadRequestException('Missing stripe signature');
}
console.log(req.rawBody); // undefined
let event;
try {
// Construct the event using raw request body, signature, and webhook secret
event = this.stripe.webhooks.constructEvent(req.rawBody, signature, webhookSecret);
} catch (err) {
console.error(err.message);
throw new Error('Webhook signature verification failed.');
}
console.log(event);
}
}
在 main.ts 中,我已使用 rawBody: true:
配置了 Nest.js 应用程序app = await NestFactory.create<NestExpressApplication>(AppModule, {
rawBody: true,
});
尽管在我的应用程序配置中设置了 rawBody: true ,但我的控制器中的 req.rawBody 仍然未定义。如何正确访问原始请求正文以解决 Stripe webhook 签名验证错误?
对我有用的几乎与你写的一样 - with "@nestjs/core": "10.3.9":
async function bootstrap() {
const app = await NestFactory.create(AppModule, { rawBody: true });
}
// Controller
@Controller('billing')
export class BillingController {
@Post('webhook') webhook(@Req() request: RawBodyRequest<Request>) {
return this.billingService.webhook(request);
}
}
// Service
@Injectable()
export class BillingService {
constructor(private readonly stripe: StripeService) {}
webhook(request: RawBodyRequest<Request>) {
Logger.log(`Received Stripe Webhook.`, 'BillingService.webhook');
const header = request.header('Stripe-Signature') ?? '';
return from(
this.stripe.stripe.webhooks.constructEventAsync(
request.rawBody,
header,
process.env.STRIPE_WEBHOOK_SECRET,
),
).pipe(
catchError((err) => {
Logger.error(err, 'BillingService.webhook');
}),
concatMap((event) => {
// Extract the object from the event.
const dataObject = event.data.object;
// ...
return of({});
}),
);
}
}
@Injectable()
export class StripeService {
public readonly stripe: Stripe;
constructor(
@Inject(MODULE_OPTIONS_TOKEN) private options: StripeModuleOptions,
) {
this.stripe = new Stripe(this.options.apiKey, this.options.options);
}
}
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<StripeModuleOptions>()
.setClassMethodName('forRoot')
.build();