Next.jsでStripeのWebhook(payment_intent.succeeded)を受け取る


Striep + 各言語の実装例は公式ドキュメントに載っています。
Next.jsの場合はbody-parserでbodyをrawにする設定ができず、StripeSignatureVerificationErrorというエラーが発生しました。
公式ドキュメントとは少し違う実装でうまくいったので紹介します。

StripeからローカルサーバーにWebhookを受け取るための外部公開設定

ローカルサーバーを起動した状態でngrokで外部からも叩けるようにします。(Stripe CLIを使っても実現可能ですが今回はngrokで。)

$ yarn dev
$ ngrok http 3000

発行されたURLはStriepの管理画面の設定で使用します。

StripeのWebohook設定

Stripeの管理画面に入ります。
テストデータの表示中にチェックを入れてで 開発 > Webhook に移動します。

今回、送信イベントにはpayment_intent.succeededを使用しています。

コード例

pages/api/stripe-webhook.ts
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
// Webhookの署名シークレットキー
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET_KEY;

// bodyParseを無効
export const config = {
  api: {
    bodyParser: false,
  },
}

export default async(req: any, res: any) => {
  const rawBody = await webhookPayloadParser(req);
  const stripeSignature = req.headers['stripe-signature'];

  let event = null;
  try {
    event = stripe.webhooks.constructEvent(rawBody, stripeSignature, endpointSecret);
  } catch (err) {
    // invalid signature
    res.status(400).end();
    return;
  }

  let intent = null;
  switch (event['type']) {
    case 'payment_intent.succeeded':
      intent = event.data.object;
      // 購入情報の書き込み処理など
      await createUserCheckoutPostByAdmin();
      console.log("Succeeded:", intent.id);
      break;
    case 'payment_intent.payment_failed':
      intent = event.data.object;
      const message = intent.last_payment_error && intent.last_payment_error.message;
      console.log('Failed:', intent.id, message);
      break;
  }
};

const webhookPayloadParser = (req: any): Promise<any> =>
  new Promise((resolve) => {
    let data = "";
    req.on("data", (chunk: string) => {
      data += chunk;
    });
    req.on("end", () => {
      resolve(Buffer.from(data).toString());
    });
  }
);

参考