在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 中间件

然后定义三个不同的中间件:

  1. 给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();
}
}
  1. 给其他地方用的普通的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 {}

大功告成!

在NestJS 中添加对Stripe 的WebHook 验证的更多相关文章

  1. 怎么添加对Shopify 的WebHook 验证

    怎么添加对Shopify 的WebHook 验证 背景介绍 Shopify 是一家一站式SaaS 模式的电商服务平台,总部位于加拿大首都渥太华,专注于为跨境电商用户提供海外品牌建立及销售渠道管理.为电 ...

  2. 在Jenkins管道中添加Webhook

    你有没有尝试过在Jenkins中添加GitHub webhook?在这篇博客中,我将演示在您的管道中添加webhook的最简单方法. 首先,什么是webhook?webhook的概念很简单.webho ...

  3. 在jekyll模板博客中添加网易云模块

    最近使用GitHub Pages + Jekyll 搭建了个人博客,作为一名重度音乐患者,博客里面可以不配图,但是不能不配音乐啊. 遂在博客里面引入了网易云模块,这里要感谢网易云的分享机制,对开发者非 ...

  4. 在Linux(Luna)下向Launch启动器中添加图标

    记录下在Luna下向Launch中添加图标的步骤,以供以后参考,这里我以加入eclipse图标为例: 首先,我们来创建一个desktop文件(Luna中到启动器Launch可以看作是Ubuntu中到桌 ...

  5. 用Retrofit发送请求中添加身份验证

    用Retrofit发送请求中添加身份验证====================在安卓应用开发中, retrofit可以极大的方便发送http网络请求,不管是GET, POST, 还是PUT, DEL ...

  6. 在html中添加script脚本的方法和注意事项

    在html中添加script脚本有两种方法,直接将javascript代码添加到html中与添加外部js文件,这两种方法都比较常用,大家可以根据自己需要自由选择 在html中添加<script& ...

  7. MVC学习随笔----如何在页面中添加JS和CSS文件

    http://blog.csdn.net/xxjoy_777/article/details/39050011 1.如何在页面中添加Js和CSS文件. 我们只需要在模板页中添加JS和CSS文件,然后子 ...

  8. 怎样在Windows资源管理器中添加右键菜单以及修改右键菜单顺序

    有时,我们需要在Windows资源管理器的右键菜单中添加一些项,以方便使用某些功能或程序. 比如我的电脑上有一个免安装版的Notepad++,我想在所有文件的右键菜单中添加一项用Notepad++打开 ...

  9. 在WebStorm环境中给nodejs项目中添加packages

    照前文 http://www.cnblogs.com/wtang/articles/4133820.html  给电脑设置了WebStorm的IDE的nodejs开发环境.新建了个express的网站 ...

随机推荐

  1. 适用于Windows和Linux的Yolo-v3和Yolo-v2(下)

    适用于Windows和Linux的Yolo-v3和Yolo-v2(下) 如何训练(检测自定义对象): (培养老YOLO V2 yolov2-voc.cfg,yolov2-tiny-voc.cfg,yo ...

  2. 尚硅谷Java——宋红康笔记【day19-day24】

    day19 测试Thread中的常用方法: start():启动当前线程:调用当前线程的run() run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 curr ...

  3. 菜鸟刷题路:剑指 Offer 03. 数组中重复的数字

    剑指 Offer 03. 数组中重复的数字 哈希表/set class Solution { public int findRepeatNumber(int[] nums) { HashSet< ...

  4. 【NX二次开发】Block UI RGB颜色选择器

    属性说明 常规         类型 描述     BlockID     String 控件ID     Enable     Logical 是否可操作     Group     Logical ...

  5. Mybatis 中经典的 9 种设计模式!面试可以吹牛了

    虽然我们都知道有23个设计模式,但是大多停留在概念层面,真实开发中很少遇到.Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式. Mybatis至少 ...

  6. 【题解】10-19秀秀的森林(forest)

    我恨秀秀倍增LCA+离线 (时光倒流) 题目 秀秀有一棵带n个顶点的树T,每个节点有一个点权ai-.有一天,她想拥有两棵树,于是她从T中删去了一条边.第二天,她认为三棵树或许会更好一些.因此,她又从她 ...

  7. csp-s模拟测试41「夜莺与玫瑰·玫瑰花精·影子」

    夜莺与玫瑰 题解 联赛$T1$莫比乌斯$\%\%\%$ $dead$  $line$是直线 首先横竖就是$n+m$这比较显然 枚举方向向量 首先我们枚举方向向量时只枚举右下方向,显然贡献$*2$就是所 ...

  8. Terraform状态State管理,让变更有记录

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 简介 最近工作中用到了Terraform,权当学习记录一下,希望能帮助到其它人. Terraform系列文章如下: Ter ...

  9. Spring学习日记02_IOC_属性注入_其他类型属性

    ICO操作Bean管理(xml注入其它类型属性) 字面量 null值 <property name="address"> <null></null&g ...

  10. C# datagridview 这是滚动条位置

    1.datagridview 设置 表格内容铺满,内容自动换行 dataGridView1.DefaultCellStyle.WrapMode = DataGridViewTriState.True; ...