在 NestJS 中增加对 Stripe 的 WebHook 验证
背景介绍
Nest 是一个用于构建高效,可扩大的 NodeJS 服务器端应用程序的框架。它应用渐进式 JavaScript, 内置并齐全反对 TypeScript, 但依然容许开发人员应用纯 JavaScript 编写代码。并联合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应编程)的元素。
Stripe 是一家美国金融服务和软件即服务公司,总部位于美国加利福尼亚州旧金山。次要提供用于电子商务网站和挪动应用程序的领取处理软件和应用程序编程接口。2020 年 8 月 4 日,《苏州高新区 · 2020 胡润寰球独角兽榜》公布,Stripe 排名第 5 位。
注:接下来的内容须要有 NodeJS 及 NestJS 的应用教训,如果没有须要另外学习如何应用。
代码实现
1. 去除自带的 Http Body Parser.
因为 Nest 默认会将所有申请的后果在外部间接转换成 JavaScript 对象,这在个别状况下是很不便的,但如果咱们要对响应的内容进行自定义的验证的话就会有问题了,所以咱们要先替换成自定义的。
首先,在根入口启动利用时传入参数禁用掉自带的 Parser.
import {NestFactory} from '@nestjs/core';
import {ExpressAdapter, NestExpressApplication} from '@nestjs/platform-express';
// 利用根
import {AppModule} from '@app/app/app.module';
// 禁用 bodyParser
const app = await NestFactory.create<NestExpressApplication>(
AppModule,
new ExpressAdapter(),
{cors: true, bodyParser: false},
);
2. Parser 中间件
而后定义三个不同的中间件:
- 给 Stripe 用的 Parser
// raw-body.middleware.ts
import {Injectable, NestMiddleware} from '@nestjs/common';
import {Request, Response} from 'express';
import * as bodyParser from 'body-parser';
@Injectable()
export class RawBodyMiddleware implements NestMiddleware {use(req: Request, res: Response, next: () => any) {bodyParser.raw({type: '*/*'})(req, res, next);
}
}
// raw-body-parser.middleware.ts
import {Injectable, NestMiddleware} from '@nestjs/common';
import {Request, Response} from 'express';
@Injectable()
export class RawBodyParserMiddleware implements NestMiddleware {use(req: Request, res: Response, next: () => any) {req['rawBody'] = req.body;
req.body = JSON.parse(req.body.toString());
next();}
}
- 给其余中央用的一般的 Parser
// json-body.middleware.ts
import {Request, Response} from 'express';
import * as bodyParser from 'body-parser';
import {Injectable, NestMiddleware} from '@nestjs/common';
@Injectable()
export class JsonBodyMiddleware implements NestMiddleware {use(req: Request, res: Response, next: () => any) {bodyParser.json()(req, res, next);
}
}
基于下面的两个不同的场景,在根 App 外面给注入进去:
import {Module, NestModule, MiddlewareConsumer} from '@nestjs/common';
import {JsonBodyMiddleware} from '@app/core/middlewares/json-body.middleware';
import {RawBodyMiddleware} from '@app/core/middlewares/raw-body.middleware';
import {RawBodyParserMiddleware} from '@app/core/middlewares/raw-body-parser.middleware';
import {StripeController} from '@app/events/stripe/stripe.controller';
@Module()
export class AppModule implements NestModule {public configure(consumer: MiddlewareConsumer): void {
consumer
.apply(RawBodyMiddleware, RawBodyParserMiddleware)
.forRoutes(StripeController)
.apply(JsonBodyMiddleware)
.forRoutes('*');
}
}
这里咱们对理论解决 WebHook 的相干 Controller 利用了 RawBodyMiddleware, RawBodyParserMiddleware 这两个中间件,它会在原来的转换后果根底上增加一个未转换的键,将 Raw Response 也返回到程序内以作进一步解决;对于其余的中央,则全副应用一个默认的,和内置那个成果一样的 Json Parser.
3. Interceptor 校验器
接下来,咱们写一个用来校验的 Interceptor. 用来解决验证,如果失常则通过,如果校验不通过则间接拦挡返回。
import {
BadRequestException,
CallHandler,
ExecutionContext,
Injectable,
Logger,
NestInterceptor,
} from '@nestjs/common';
import Stripe from 'stripe';
import {Observable} from 'rxjs';
import {ConfigService} from '@app/shared/config/config.service';
import {StripeService} from '@app/shared/services/stripe.service';
@Injectable()
export class StripeInterceptor implements NestInterceptor {
private readonly stripe: Stripe;
private readonly logger = new Logger(StripeInterceptor.name);
constructor(
private readonly configService: ConfigService,
private readonly stripeService: StripeService,
) {
// 等同于
// this.stripe = new Stripe(secret, {} as Stripe.StripeConfig);
this.stripe = stripeService.getClient();}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {const request = context.switchToHttp().getRequest();
const signature = request.headers['stripe-signature'];
// 因为 Stripe 的验证是不同的 WebHook 有不同的密钥的
// 这里只须要依据业务的需要减少对应的密钥就行
const CHARGE_SUCCEEDED = this.configService.get('STRIPE_SECRET_CHARGE_SUCCEEDED',);
const secrets = {'charge.succeed': CHARGE_SUCCEEDED,};
const secret = secrets[request.body['type']];
if (!secret) {
throw new BadRequestException({
status: 'Oops, Nice Try',
message: 'WebHook Error: Function not supported',
});
}
try {this.logger.log(signature, 'Stripe WebHook Signature');
this.logger.log(request.body, 'Stripe WebHook Body');
const event = this.stripe.webhooks.constructEvent(
request.rawBody,
signature,
secret,
);
this.logger.log(event, 'Stripe WebHook Event');
} catch (e) {this.logger.error(e.message, 'Stripe WebHook Validation');
throw new BadRequestException({
status: 'Oops, Nice Try',
message: `WebHook Error: ${e.message as string}`,
});
}
return next.handle();}
}
4. 利用到 Controller
最初,咱们把这个 Interceptor 加到咱们的 WebHook Controller 上。
import {Controller, UseInterceptors} from '@nestjs/common';
import {StripeInterceptor} from '@app/core/interceptors/stripe.interceptor';
@Controller('stripe')
@UseInterceptors(StripeInterceptor)
export class StripeController {}
功败垂成!