💰

Python(Django)のStripe実装にWebhookを使ってイベントを受け取る②

2022/01/31に公開

はじめに

株式会社var でインターンをしているひらはらです。

先日PythonのフレームワークであるDjangoを用いて決済サービスであるStripeの実装を行いました。Stripeを実装した際の記事については以下より御覧ください。

https://zenn.dev/var/articles/65785548e340fc

その際、ユーザーのサブスクリプション処理が失敗した際にStripe側で発生したイベントをWebアプリケーション側でも受信し、任意の処理を実装する必要があったため、StripeのWebhookを利用した実装を行いました。

Webhookとは?

Webhookを利用することで、特定のイベントが発生した際にHTTPのPOSTリクエストを利用してWebアプリケーション側へ通知し、任意の処理を実行することができます。

Webhookを使った例

下記のような処理はWebhookを利用することで実装できます。

  • Githubのリポジトリに変更があった場合にSlackのチャンネルに通知する
  • Stripeで決済が完了した場合にSlackのチャンネルに通知する
  • Stripeで決済が失敗した場合にSlackのチャンネルに通知する

環境

python 3.10.1
django 3.2.5

準備

Pythonでstripeを操作するためにstripeパッケージをインストールしておきます。

pip install stripe

実装

それでは実際に実装していきます。

Stripeの公式ドキュメントでWebhookについての詳細が記載されています。

https://stripe.com/docs/webhooks#webhooks-def

まずは、Stripeを使うためのキーを環境変数から取得しています。こちらはStripeのダッシュボードから取得することができます。

# stripeを扱うシークレットキー
stripe.api_key = os.getenv('STRIPE_SECRET_KEY')
#  stripeのwebhookを扱うシークレットキー
endpoint_secret = os.getenv('WEBHOOK_ENDPOINT_SECRET')

次にStripeからのリクエストを受け取る関数を作ります。

Stripeの公式ドキュメントではpayloadの部分がpayload = request.body でしたが、payload = request.body.decode('utf-8')でないとエラーが発生します。

@require_POST
@csrf_exempt
def stripe_webhook(request):
  payload = request.body.decode('utf-8')
  sig_header = request.META['HTTP_STRIPE_SIGNATURE']
  event = None

  try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
  except ValueError as e:
    # Invalid payload
    return HttpResponse(status=400)

  except stripe.error.SignatureVerificationError as e:
        return HttpResponse(status=400)

このリクエストは設定したURLがわかれば誰でも送信できるため、本当にStripeからのリクエストなのか?をhttpのヘッダーにあるMETAタグ部分を参照し、Stripeの署名が有る場合のみ実行するようにしています。

詳細なStripeの署名については下記を閲覧下さい。

https://stripe.com/docs/webhooks/signatures

そして最後にStripe側でのイベントが発生した際に任意の処理を実行して完成です。

  if event['type'] == 'invoice.payment_failed':
	# 任意の処理
    return HttpResponse(status=200)

ちなみに取得したいStripeで発生したイベントは下記のAPIドキュメントに記載されていますが、かなり多くのイベントがあります。そのため、任意のイベントをこのAPIドキュメントから探すのが難関です、、
https://stripe.com/docs/api/events/types

ちなみにStripeから送られる情報は下記の様なオブジェクトになっており、type を見てどんなイベントなのか?を判別しています。

// 公式サンプル
{
  "id": "evt_1KNnu8FLrDY5HvLeZtyC7cDf",
  "object": "event",
  "api_version": "2020-08-27",
  "created": 1643589480,
  "data": {
    "object": {
      "object": "balance",
      "available": [
        {
          "amount": 9476423,
          "currency": "jpy",
          "source_types": {
            "card": 9476423
          }
        }
      ],
      "livemode": false,
      "pending": [
        {
          "amount": 417986,
          "currency": "jpy",
          "source_types": {
            "card": 417986
          }
        }
      ]
    }
  },
  "livemode": false,
  "pending_webhooks": 0,
  "request": {
    "id": null,
    "idempotency_key": null
  },
  "type": "balance.available"
}

下記がソースコードです。

# stripeを扱うシークレットキー
stripe.api_key = os.getenv('STRIPE_SECRET_KEY')
# stripeのwebhookを扱うシークレットキー
endpoint_secret = os.getenv('WEBHOOK_ENDPOINT_SE')

@require_POST
@csrf_exempt
def stripe_webhook(request):
    payload = request.body.decode('utf-8')
    sig_header = request.META['HTTP_STRIPE_SIGNATURE']
    event = None

    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )

    except ValueError as e:
        return HttpResponse(status=400)

    except stripe.error.SignatureVerificationError as e:
        return HttpResponse(status=400)

    if event['type'] == 'invoice.payment_failed':
		  # 任意の処理

    return HttpResponse(status=200)

ローカルでのデバック時

Stripe CLIを利用することで、指定したイベントを手動で発生させて簡単にデバックすることができます。

詳細は下記を閲覧下さい。

https://stripe.com/docs/webhooks/test

Webhookの本番運用時

実際に本番環境でWebhookを利用する際にはStripeのダッシュボードからStripeからPOSTリクエストを受け取るURLと受け取りたいイベントの2つをStripeのダッシュボードで設定する必要があるのでご注意下さい。

まとめ

今回はPython(Django)においてStripeでのWebhookの実装を行いました。

Webhookを利用することで、簡単にStripe側で発生したイベントをWebアプリケーション側でも受信して任意の処理を実行することが可能になります。

宣伝

弊社では、インフラ学習サイトEnvader(エンベーダー)とITスクール(RareTECH)を運営しています。また、企業研修やシステム開発なども行っていますので興味がある方はHPよりご連絡下さい。

https://envader.plus

https://raretech.site

https://var.co.jp

GitHubで編集を提案

Discussion