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

SharedArrayBuffer updates in Android Chrome 88 and Desktop Chrome 92

クロスオリジン分離対応を実施

Firebase Emulator Suiteで起動しているFunctionsから本番のFirestoreにアクセスする

functionsのみエミュレータを使うようにするとできる

Firebase Functions呼び出し時に Error: function terminated. が発生した場合

firebase functions:logで詳細を確認できる

Cloud BuildでFirebase Hostingのデプロイを行う

リポジトリへのpush以外をトリガーにしたい場合に使用

Firebase FunctionsでonCallで実装しているにも関わらずCORSエラーが発生した場合

Cloud Functions(GCP)の管理画面を確認してみる

JestでFirestoreセキュリティルールのテストを書く

Github ActionsでCIを回せるようになった