DjangoでStripeのCheckoutを使って決済システムを実装する方法を徹底解説

2022年2月11日Djangoの豆知識Stripe,おもしろライブラリ,決済系

この記事では、Stripeの一番簡単な実装方法であるCheckoutをDjangoでどのように実装すればよいかについて、サンプルコードを交えながら丁寧に解説していきます。

ちなみに私は数あるWebサービスの中で、Stripeが特に好きです。

ダッシュボードが見やすい、アプリも使いやすい、審査がスムーズ、サンプルコードが言語やライブラリごとにあって充実している、様々な実装方法が選択可能、Apple Payなど使える、etc、上げるときりがありません。

実際に日本で言うと、ANAやDeNAなど名だたる会社に利用されています。

Stripeの実装方法の選択肢について

Stripeのよく使われる実装方法はざっくり2通りあります。1つがCheckout、もう一つがElementsです。

Checkoutとは?

この記事のテーマです。ざっくり言うと、「Stripeにほとんどおまかせ」する方法です。今回の完成イメージはこんな感じです。

Elementsとは?

ちょっと理解と実装が難しいです。ざっくり言うと、「いろいろカスタマイズしてUIUXを追求したい」時に使う方法です。

どちらの方法もクールです。まずは簡単なCheckoutで理解を深めてElementsを学ぶのがいいと思います。それではCheckoutしていきましょう!完成イメージはこんな感じです。

参考:Python用の公式ドキュメント

ざっくり要点まとめ

やるべきことをざっくりまとめると、

StripeのシークレットキーをつけてCheckoutのAPIをたたき、決済ページのURLをもらう。ユーザをそのURLにリダイレクトさせる。決済が完了したら、Stripeのサーバーから情報がWebhookの形式で送られてくるのでそれを処理しデータベース等に記録する

です。APIを叩いたり、StripeのサーバーからWebhookを受け取ったりというあたりは公式のSDKがだいたいやってくれるので簡単です。

サンプルコードと動かすために必要なもの

Githubにあげています。

git clone -b stripe_checkout https://github.com/yeconnect/django-baby-starter-template.git

cd django-baby-starter-template

docker compose up --build

バージョン

Python 3.9

Django==3.2

stripe==2.65.0

ステップ1: Stripeに会員登録

こちらから会員登録してください。

その後メール認証を済ませたら、ダッシュボードの左上のアイコンから「編集」を押して、アカウント名をつけてください。今回はhogehogeと設定しました。この設定は必ず行ってください。しないとこの先エラーが出ます。

ステップ2: Stripeダッシュボードで秘密鍵を取得

「開発者」というボタンを押して、APIキータブに行き、シークレットキーを取得します。(秘密鍵はとても大事なので、テストキーを表示と書いてあるボタンを押すと表示されます。)

この秘密鍵は後で使います。

ステップ3: Stripeダッシュボードで適当に商品を作る

上の方にある「商品」を押して商品ページに飛んでください。

その後、下の写真でいう右上にある「商品を追加」ボタンを押します。

適当に商品名、値段を設定し、支払い方法は「一括」を選択してください。このとき値段は50円以上でないと後にエラーがでます。

作成できたら値段IDをメモってください。price_から始まるものです。

ちなみに上の値段IDは1円で設定したので後にエラーがでて後悔しました。

この値段IDも後で使います。

ステップ4: StripeのPython SDKをインストール

面倒な実装をいろいろやってくれるStripeのPython SDKをインストールしましょう。サンプルコードの場合はrequirements.txtにすでに記載しているのでする必要はありません。

pip install stripe==2.65.0

ステップ5: サンプル商品ページ、決済成功ページ、決済商品ページを作成

サンプル商品ページをhttp://localhost:4989/bookstore/checkout に、

決済成功時に帰るページをhttp://localhost:4989/bookstore/successに、

決済商品ページをhttp://localhost:4989/bookstore/cancelに書いています。

単純なHTMLページです。冗長ですが、

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('bookstore/', include('bookstore.urls'))
]
from django.urls import path
from . import views

app_name = 'bookstore'

urlpatterns = [
    path('checkout',views.checkout,name='checkout'),
    path('success',views.success,name='success'),
    path('cancel',views.cancel,name='cancel'),
    path('create-checkout-session',views.create_checkout_session,name='cancel'),
    path('webhook', views.handle_webhook, name='webhook'),
]
import stripe
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt

stripe.api_key = 'ここにあなたのシークレットキー'

def checkout(request):
    return render(request, "checkout.html")

def success(request):
    return render(request, "success.html")

def cancel(request):
    return render(request, "cancel.html")

# 中略

サンプルコードを使っていない場合はルートディレクトリのtemplates配下にHTMLファイルを3つ作ってください。

そしてDjangoにtemplatesにHTMLを置いたということを伝えてあげましょう。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],  # 追加
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

これで3つのページは完成です。

サンプルコードを使っている場合は、docker compose up --buildでサーバーを起動し

http://localhost:4989/bookstore/checkout を表示してみましょう。

こんな感じで表示されたらOKです。

ステップ6: StripeのAPIを叩いてURLをもらう部分を作成

それではやっと本質部分を書いていきましょう。決済ページのURLをStripeにもらってそこにユーザを飛ばす関数create_checkout_sessionを作ります。必要なところを抽出するとこんな感じです。

まず、先程用意した秘密鍵をstripe.api_keyに、値段IDをPRICE_IDに入れましょう。

import stripe
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt

stripe.api_key = 'ここにあなたのシークレットキー'

@csrf_exempt
def create_checkout_session(request):
    MY_DOMAIN = f'{request.scheme}://{request.get_host()}'
    PRICE_ID = "あなたの値段ID"
    try:
        checkout_session = stripe.checkout.Session.create(
            line_items=[
                {
                    'price': PRICE_ID,
                    'quantity': 1,
                },
            ],
            mode='payment',
            success_url=MY_DOMAIN +
            '/bookstore/success?session_id={CHECKOUT_SESSION_ID}',
            cancel_url=MY_DOMAIN + '/bookstore/cancel',

            # これは任意 (メタデータの追加指定ができる)
            payment_intent_data={
                "metadata": {
                    "user_hogehoge": "Django Baby",
                }
            }
        )
        print("決済ページのURLはこれ →" + checkout_session.url)
        return redirect(checkout_session.url)
    except Exception as e:
        print("エラー内容:"+str(e))
        # ここもう少しハンドリングするべき

決済ページの URL を Stripe からもらうには、stripe.checkout.Session.create を呼び出して Session オブジェクトを作成します。このとき、必要な情報をキーワード引数の形式で渡します。

  • line-items : どの商品をチェックアウトするのか、値段IDを使って指定する。
  • mode : 'payment’ を指定する。
  • success_url : 決済が完了したときに表示するページを指定する。
  • cancel_url : 決済がキャンセルされたときに表示するページを指定する。
  • payment_intent_data (任意) : メタデータを指定する。メタデータは後で Webhook で受け取ることになる。

Session オブジェクトが無事に作成されたら、url フィールドに決済ページの URL が代入されています。そこにリダイレクトして関数 create_checkout_session の処理を終了します。

ステップ7: 想定通り決済できることを確認

それでは実際にhttp://localhost:4989/bookstore/checkout からテスト購入してみましょう!

Stripeには開発のテストのためのクレジットカード番号が用意されています。(Stripeには限らずですが、)

カード番号のところでは4242 4242 4242 4242 と入れてください。他は文句を言われないように入力をしていくと決済が完了します!

ステップ8: Webhookを使って支払いしたことを確認する

だいたい形になってきました。しかし「ユーザがちゃんと決済したかどうかをどうやって判断したらいいの?」と思う人が多いのではないでしょうか?

現在のStripeではWebhookという方法が利用されています。これは決済が完了した瞬間にStripeがそのイベントをHTTPのPOSTメソッドで送ってくれるものです。どのURLに送るかは各自ダッシュボードで設定できます。

例えばあなたがhttps://example.com/bookstore/webhook に送るように設定すればそこに送ってくれるので、それに合わせてDBへの記録等をするコードを書けばよいわけです。

これは少し複雑なのでステップ8-1, 8-2, 8-3, 8-4と分けて説明します。

ステップ8-1: Stripe CLIをPCにインストールする。

デプロイした後に関しては、ドメイン名をStripeに登録するだけでそこに送ってくれるので、Webhookを受け取るのは簡単です。

しかしローカルで開発しているとき、あなたのURLはhttp://localhost:4989/hoge/hogeとなっています。Stripeからしたら「どこのlocalhostだよ、」となってしまうので、それを伝えて接続する必要があります。そのために必要なのがStripe CLIです。

(ここからは新たに別のターミナルでやったほうがいいです。)

こちらに各OSごとのインストール方法が書いてあります。

Macの方は

brew install stripe/stripe-cli/stripe

でOKです。

Windowsの方は、前やったとき少し面倒だった覚えがあるのですが、https://github.com/stripe/stripe-cli/releases/latest に行って、stripe_X.Y.Z_windows_x86_64.zip (X, Y, Zは数字です)をダウンロードし、解凍してください。そうするとstripe.exeというファイルがあるので、configやbookstoreディレクトリと同階層に置いてください。

インストール完了です。

ステップ8-2: Stripe CLIからStripeにログインする。

stripe login

と実行し、エンターキーを押すとブラウザが開きます。

「アクセスを許可」を押すと接続準備が完了です。

ステップ8-3: Stripeにローカル用のWebhook URLを伝える。

それではstripeに、「このパソコンのlocalhost:4989/bookstore/webhookにWebhookを投げて!」と伝えましょう

stripe listen --forward-to localhost:4989/bookstore/webhook

そうするとターミナルでwhsec_〇〇〇〇〇〇というwebhookシークレット文字列が表示されます。

これは後で使います。

ステップ8-4: DjangoでWebhookを処理する関数を作成

完成版からお見せするとこんな感じです。先程のwhsec_〇〇〇〇〇〇をendpoint_secretに代入しています。

# 中略
endpoint_secret = 'whsec_〇〇〇〇〇〇'

@csrf_exempt
def handle_webhook(request):
    payload = request.body
    sig_header = request.META['HTTP_STRIPE_SIGNATURE']
    event = None

    try:
        # Stripeが送ってきたものか判定
        event = stripe.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
    except ValueError:
        # Payloadが変な時
        return HttpResponse(status=400)
    except stripe.error.SignatureVerificationError:
        # Stripeでない第三者が不正に送ってきた時
        return HttpResponse(status=400)

    # eventのtypeによって、好きなように分岐
    if event['type'] == 'payment_intent.succeeded':
        payment_intent = event['data']['object']
        metadata = payment_intent["metadata"]
        print('決済が完了したのでここで決済処理をします!!!')
        print("決済の詳細情報→" + str(payment_intent))
        print("指定したメタデータの取り出し→" + str(metadata))

    else:
        event_type = event['type']
        print(f'Event type {event_type}')
    return HttpResponse(status=200)

sig_headerは、Stripeの署名です。これをendpoint_secretを使って検証することで本当にStripeから送られてきたかを判定します。(決済の判定で、悪意ある第三者からのデータを受け付けるとやばいですよね。)

署名の検証が終わったら、変数eventにすべてのデータが入っているのでこれを使って各自のビジネスロジックを書けばよいのではないでしょうか?

ちなみにStripeは決済が完了した時のみデータを送ってくるわけではありません。event['type’]の値によってうまく分岐処理しましょう!

また実際の決済となるとユーザIDなども必要です。その場合は決済URLを作るタイミングでpayment_intent_dataに刻んでおきましょう。Webhookのデータから参照できます。詳しくはサンプルコードをご覧ください。

ステップ9: この後やるべきこと

これができたら本番で使いたいですね。今はテストモードなので、「本番環境利用の申請」ボタンからStripeに申請しましょう。審査に通るとついに本物のカードでも決済できるようになります!審査は割とすぐ通る印象です。最高ですね!

参考:「決済サービス「Stripe」のアカウント作成〜サブスクリプションの設定まとめ

おまけ

Stripeの快適さを体験できたら嬉しく思います!

ちなみにこれとほとんど同じ方法でPayPayの決済組み込みもできます!

DjangoでPayPayのAPIを使って決済をするハンズオン、サンプルコード」でぜひ試して遊んでみてください。

DjangoではなくPythonレベルで知りたい方は、「PayPayのAPIをPythonで体験してみよう」でも解説しています。