Cloud Functions for FirebaseでStripeのWebhook Endpointを作る

公開日時

StripeのPayment Intents APIを使って決済を行う際に、サーバ側はWebhookの payment_intent.succeeded イベントで決済完了の判定を行う。

今回はFirebaseを使っていたので、WebhookのエンドポイントをCloud Functionsで用意することにした。

WebhookエンドポイントはHTTPリクエスト経由で呼ばれるので、onCallではなくonRequestを用いて実装を行った。

なお、今回もstripe-firebase-extensionsのコードを参考にさせてもらった。

import * as functions from 'firebase-functions'
import Stripe from 'stripe'
import { getWebhookEvent } from '../util/stripe'
import { getUidFromStripeCustomerId, updatePaymentDocument } from '../util/firestore'

const handleWebhookEvents = functions
  .region('asia-northeast1')
  .https.onRequest(async (req, resp) => {
    const relevantEvents = new Set(['payment_intent.succeeded'])
    let event: Stripe.Event

    const signature = req.headers['stripe-signature']
      ? (req.headers['stripe-signature'] as string)
      : null
    if (!signature) {
      console.error('️[Error]: Webhook signature verification failed.')
      resp.status(401).send('Webhook Error: Invalid Secret')
      return
    }

    try {
      event = getWebhookEvent({
        payload: req.rawBody,
        signature,
      })
    } catch (error) {
      console.error(
        '️[Error]: Webhook signature verification failed. Is your Stripe webhook secret parameter configured correctly?',
        error.message
      )
      resp.status(401).send('Webhook Error: Invalid Secret')
      return
    }

    if (relevantEvents.has(event.type)) {
      console.log(`Handling Stripe event [${event.id}] of type [${event.type}].`)
      try {
        switch (event.type) {
          case 'payment_intent.succeeded': {
            const paymentIntent = event.data.object as Stripe.PaymentIntent
            const customer = paymentIntent.customer
            if (!customer) {
              throw new Error('customer is null')
            }
            const uid = await getUidFromStripeCustomerId(customer as string)
            if (!uid) {
              throw new Error('uid not found')
            }

            await updatePaymentDocument({
              uid,
              paymentIntentId: paymentIntent.id,
              status: paymentIntent.status,
            })
            break
          }
          default:
            new Error('Unhandled relevant event!')
        }
        console.log(`Successfully handled Stripe event [${event.id}] of type [${event.type}].`)
        resp.json({
          message: 'ok',
        })
      } catch (error) {
        console.error(
          `️[Error]: Webhook handler for Stripe event [${event.id}] of type [${event.type}] failed:`,
          error.message
        )
        resp.status(500).json({
          error: 'Webhook handler failed. View function logs in Firebase.',
        })
        return
      }
    }
  })

export default handleWebhookEvents

Related #firebase

YouTubeの「Firebase Release Notes」プレイリスト

最新のアップデートの概要を把握するのにちょうど良い

Firebase Extensions

Functionsを実装する際の参考になる

Cloud FunctionsでFirebase Authenticationの認証情報を取得する

functions.https.onCallを使用している場合はcontextパラメータを受け取ることができる

Nuxt.jsのservice workerで環境変数を切り替えられるようにする

設定情報をハードコードしたくなかったので、ビルド時にnodeコマンドでファイル生成を行い環境変数を切り替えられるように対応した。

CloudFunctionsを使ってFirestoreのサブコレクションを削除する

CloudFunctions内でFirebase CLIのdeleteコマンドを呼び出すことで一括削除ができる