在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. Java IO学习笔记一:为什么带Buffer的比不带Buffer的快

    作者:Grey 原文地址:Java IO学习笔记一:为什么带Buffer的比不带Buffer的快 Java中为什么BufferedReader,BufferedWriter要比FileReader 和 ...

  2. 菜鸟刷题路:剑指 Offer 05. 替换空格

    剑指 Offer 05. 替换空格 class Solution { public String replaceSpace(String s) { StringBuilder str = new St ...

  3. Spring Security 快速上手

    Spring Security 框架简介 Spring Security 说明 Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案 关于安全方面的两 ...

  4. Django(65)jwt认证原理

    前言 带着问题学习是最有目的性的,我们先提出以下几个问题,看看通过这篇博客的讲解,能解决问题吗? 什么是JWT? 为什么要用JWT?它有什么优势? JWT的认证流程是怎样的? JWT的工作原理? 我们 ...

  5. 从 Nginx 优秀的核心架构设计,揭秘其为何能支持高并发?

    目录: 1. Nginx的整体架构 2. Nginx的模块化设计 3. Nginx的请求方式处理 4. Nginx事件驱动模型 5. Nginx进程处理模型 写在前面 Nginx 是一个 免费的,开源 ...

  6. 「Spring Boot架构」集成Mybatis-Plus的实例详解

    MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发.提高效率而生. 特性 无侵入:只做增强不做改变,引入它不会对现有工程 ...

  7. 【dog与lxy】8.25题解-necklace

    necklace 题目描述 可怜的dog最终还是难逃厄运,被迫于lxy签下城下之约.这时候lxy开始刁难dog. Lxy首先向dog炫耀起了自己的财富,他拿出了一段很长的项链.这个项链由n个珠子按顺序 ...

  8. Android Studio用上Visual Studio Android Emulator

    背景介绍 第一次接触Android官方的AVD(Android Virtual Device)时你可能会吐槽又慢又丑,不要紧,微软作为新晋安卓阵营最佳开发商,其实也为我们准备了一个脱胎于Windows ...

  9. 1、mysql基础入门(2)

    1.4.常用非关系型数据库产品介绍: 1.Memcached(key-value)数据库:

  10. (C++)戳西瓜

    写在前面的话: 请不要吝啬你的点赞!!! 题目描述 有 n 个西瓜,编号为0 到 n-1,每个西瓜上都标有一个数字,这些数字存在数组 nums 中. 现在要求你戳破所有的西瓜.每当你戳破一个西瓜 i ...