💸

Nest + FastifyでStripe Webhookを受け取る[2022年8月版]

2022/08/27に公開

TL;DR

StripeのwebhookをNestで受け取ろうとすると割と手間だった

StripeのWebhookを受け取って処理を走らせようとすると割と面倒臭かった上に正確な(?)情報がほぼなかったので備忘として書いてみる

下記をやれば良いよ!

  • rawbodyでリクエストを受け取る
  • 受け取ったリクエストのheaderからstripe-signatureを取り出して検証する
  • 検証が終わったらデータをよしなに利用する

rawBodyでリクエストを受け取る

Nestはめちゃくちゃ便利なフレームワークなので、リクエストを受け取るとよしなにパースして取扱しやすくしてくれます。が、StripeのWebhookはsignatureの検証をするためにrawbodyが必要です。
わざわざめんどくさいことしやがってStripeめ

ググったらhapiの例しかなかったりbody-parser使えとか書いてあったりしてめちゃくちゃ試行錯誤したけどうまくいかない...
それっぽいissueを読んだりそれっぽいプラグインをためしたりしたもののうまくいかず...
ExpressはともかくFastifyでどうしたら...
fastify-rawbodyを使う?
などと1日を溶かして挫折して寝て翌朝重い気持ちで再度ググったら

公式に答えがあると言うオチがつきました

困ったときは公式ドキュメントを見てみよう(大反省)

と言うわけでmain.tsを書き換えます

main.ts
const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter()
+  {
+    rawBody: true,
+  }
);
await app.listen(3000,'0.0.0.0');

これでrawbodyが受け取れるようになるので、コントローラーを作成します

stripe.controller.ts
+ import { Controller, HttpException, HttpStatus, Post, RawBodyRequest, Req, } from '@nestjs/common';
+ import { FastifyRequest } from 'fastify';
+ import { StripeService } from './stripe.service';

+ @Controller()
+ class StripeController {
+   @Post('webhook/stripe')
+   handle(@Req() req: RawBodyRequest<FastifyRequest>) {
+     const sig = req.headers['stripe-signature'];
+     if (!sig)
+       throw new HttpException('missing signeature', HttpStatus.BAD_REQUEST);
+     const buf = req.rawBody;
+     const whsec = 'whsec_xxxx';
+     const stripe = new Stripe('sk_test_xxxx', { apiVersion: '2022-08-01' });
+     try {
+       const event = stripe.webhooks.constructEvent(buf, sig, whsec);
+       // stripe webhookをお好みで処理する
+       return await this.stripeService.processStripeWebhook(event);
+     } catch (error) {
+       console.error(`[StripeError]:${error}`);
+       throw error;
+     }
+   }
+ }

これでstripeのwebhookを受け取っていい感じに処理ができます
決済結果を受け取って在庫を減らしたり発送処理を駆動させたりなんだりよしなにやってください

おまけ

stripeはアレなのでeventから取り出したevent.data.objectの型付けが自動でいい感じになりません。anyの乱用はダメな文明
と言うわけで、
StripeのIssueで見つけた方法を使って強制的に型をつけてあげましょう。

sample.ts
  // StripeCheckoutを扱う場合はこう
  const checkout = event.data.object as Stripe.Checkout.Session;

  // Chargeを扱う場合はこう
  const charge = event.data.object as Stripe.Charge;

  // PaymentIntentを扱う場合はこう
  const paymentIntent = event.data.object as Stripe.PaymentIntent;

webhookを受け取って利用するのは大体決済完了を検知して在庫を増減させたり発送を行なったり領収書メールを発送したりなどの処理を行いたいときだと思うので、
上記三つのどれかでイケると思うし、違ってもいろんな型があるので、
目的に合わせてStripeの公式ドキュメントやライブラリのソースを読んだりすれば
使いたい型は結構簡単に見つけられるかと思います

このevent.data.objectにいい感じに型がつかない問題はissueが立った2020年から今まで解決されていないので、当面手動でasを使って型をつけてあげることになると思います

実際にはevent.data.objectを取り出す前にevent.typeをチェックしてswitch-case文や条件分岐を使ってどの型を当てるかを分岐させることになると思うので、
event.typeのチェックも行うようにしましょう

以上、NestでStripeWebhookを受け取って利用したいと考えた人の参考になれば幸いです
そしてStripeは早く型付けて && rawbody使わないでいい方法を導入して...

Discussion