自動化無しに生活無し

WEBとかAIとかLinux関係をひたすら書く備忘録系ブログ

【Stripe】Djangoにクレジットカード決済機能を実装させる

thumbnail

DjangoでECサイトや課金ゲームサイト等を展開しようと考えているのであれば、避けて通ることができないのがカード決済。

Stripeというカード決済代行会社を利用することで、決済処理を手軽に実装させることができる。

本記事ではその一例を紹介する。

共通設定

まず、stripeライブラリをインストールする

pip install stripe 

settings.pyにて下記をセットしておく。Stripeの秘密鍵は.gitignoreに指定したファイルに書くなど対策をしておく。

from .local_settings import *
"""
STRIPE_PUBLISHABLE_KEY  = "asdasdasdadsas"
STRIPE_API_KEY          = "sadadadadadada"
"""

ハードコードするのであれば基本共有はしないようにする。

今回は以下のように環境変数を使った。

import os

if "STRIPE_PUBLISHABLE_KEY" in os.environ and "STRIPE_API_KEY" in os.environ:
    STRIPE_PUBLISHABLE_KEY  = os.environ["STRIPE_PUBLISHABLE_KEY"]
    STRIPE_API_KEY          = os.environ["STRIPE_API_KEY"]

参照: Ubuntuに環境変数をセットし、Pythonでosモジュールを使って読む方法【os.environ使用、crontabにも対応】

最新版のやり方

最新版はApplePayに対応しているなど、レガシー版に比べて決済方法が充実している。

流れ

  1. Djangoが秘密鍵をセットし、Stripeの決済セッションを作る
  2. Djangoは公開鍵とセッションIDをテンプレートにに引き渡してレンダリング
  3. クライアントは決済ボタンを押して、カード情報を入力する
  4. 成功時、指定したページにリダイレクトされる。(この時にDjangoのモデルにデータを格納する等をする。)

ビュー

from django.shortcuts import render,redirect

from django.views import View
from .models import Topic

import stripe
from django.conf import settings
from django.urls import reverse_lazy


class IndexView(View):

    def get(self, request, *args, **kwargs):
        context = {}

        #ここでStripeのセッションを作る、暗号化用の公開鍵とセッションIDを引き渡し、決済処理を顧客にさせる。
        # https://dashboard.stripe.com/account から企業名を入れていないとエラーが出る点に注意
        # 自分の名前をローマ字で入れておく。

        #セッションを開始するため、秘密鍵をセットする。
        stripe.api_key = settings.STRIPE_API_KEY

        session = stripe.checkout.Session.create(
                payment_method_types=['card'],
                
                #顧客が購入する商品(実践ではここにカートに入れた商品を格納)
                line_items=[{
                    'price_data': {
                        'currency': 'jpy',
                        'product_data': {
                            'name': 'T-shirt',
                            },
                        'unit_amount': 2000,
                        },
                    'quantity': 1,
                    }],
                
                mode='payment',

                #決済成功した後のリダイレクト先
                success_url=request.build_absolute_uri(reverse_lazy("bbs:checkout")) + "?session_id={CHECKOUT_SESSION_ID}",

                #決済キャンセルしたときのリダイレクト先
                cancel_url=request.build_absolute_uri(reverse_lazy("bbs:index")),
                )

        print(session)

        #この公開鍵を使ってテンプレート上のJavaScriptにセットする。顧客が入力する情報を暗号化させるための物
        context["public_key"]   = settings.STRIPE_PUBLISHABLE_KEY

        #このStripeのセッションIDをテンプレート上のJavaScriptにセットする。上記のビューで作ったセッションを顧客に渡して決済させるための物
        context["session_id"]   = session["id"]

        return render(request,"bbs/index.html",context)

    def post(self, request, *args, **kwargs):

        posted  = Topic( comment = request.POST["comment"] )
        posted.save()

        return redirect("bbs:index")

index   = IndexView.as_view()


class CheckoutView(View):

    def get(self, request, *args, **kwargs):

        stripe.api_key = settings.STRIPE_API_KEY

        #セッションIDがパラメータに存在するかチェック。なければエラー画面へ
        if "session_id" not in request.GET:
            return redirect("bbs:index")

        #ここでセッションの存在チェック(存在しないセッションIDを適当に入力した場合、ここでエラーが出る。)
        try:
            session     = stripe.checkout.Session.retrieve(request.GET["session_id"])
            print(session)
        except:
            return redirect("bbs:index")

        """
        #ここで決済完了かどうかチェックできる。(何らかの方法でセッションIDを取得し、URLに直入力した場合、ここでエラーが出る。)
        try:
            customer    = stripe.Customer.retrieve(session.customer)
            print(customer)
        except:
            return redirect("bbs:index")
        """
        
        # XXX: 2回目以降の連続決済の場合、↑のsession.customerがnullになるため、この方法では決済の確認ができない
        # session.payment_status で確認をする


        print( session.payment_status )
        if session.payment_status != "paid":
            return redirect('shop:session', order.id)


        #この時点で、セッションが存在しており、なおかつ決済している状態であることがわかる。
        #TODO:実践ではここで『カート内の商品を削除する』『顧客へ注文承りましたという趣旨のメールを送信する』『注文が入った旨を関係者にメールで報告する』等の処理を書く。


        print("決済完了")


        #TODO:出来ればこのページは注文完了のレンダリングを
        return redirect("bbs:index")

checkout    = CheckoutView.as_view()

実践では成功した時、失敗したときのリダイレクト先は、直に書かず、reverse_lazyを使って逆引きをすれば良いだろう。

https://docs.djangoproject.com/en/4.0/ref/urlresolvers/

テンプレート

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
	<title>簡易掲示板</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

</head>
<body>

    <main class="container">
        <form method="POST">
            {% csrf_token %}
            <textarea class="form-control" name="comment"></textarea>
            <input type="submit" value="送信">
        </form>

        {% for topic in topics %}
        <div class="border">
            {{ topic.comment }}
        </div>
        {% endfor %}

        <button id="checkout-button" type="button">決済</button>

    </main>


<!-- Stripeクラスを使うため、CDNをインストールしておく。 -->
<script src="https://js.stripe.com/v3/"></script>
<script>

    //ここにStripeの公開鍵をセットする。
    var stripe = Stripe( "{{ public_key }}" );

    //決済ボタン押したときのイベント(Stripeへ決済処理する)をセットする。
    var checkoutButton = document.getElementById('checkout-button');

    checkoutButton.addEventListener('click', function() {
        stripe.redirectToCheckout({
          sessionId: '{{ session_id }}'
        }).then(function (result) {
            //失敗したときの処理
        });
    });

    console.log("{{ session_id }}");

</script>

</body>
</html>

動かすとこうなる

サーバーを起動し、決済ボタンを押す。すると、下記のStripeが提供しているカード入力画面が出てくる。

カード番号はテストとして用意されている4242 4242 4242 4242を入力する。カード所有者、メールアドレス、有効年月とCVCは適当で。

決済が完了したら、クライアントは、success_urlで指定した場所にリダイレクトされる。サイト管理者はStripeアカウントから、支払いを確認できる。payment_intentが記録されている。

StripeのセッションIDもイベントデータから個別に確認できる。

テストのカード番号に関しては下記を参照。

https://stripe.com/docs/testing

レガシー版のやり方

流れ

  1. 属性に公開鍵をセットしたStripeのJSが発動(ここでstripe側が公開鍵の有効性を確認している)
  2. カード番号を入力してカードの有効確認。stripeTokenにセットされDjangoにPOST文が送られる
  3. 下記のCheckoutViewのPOSTメソッドの処理が実行される。
  4. 公開鍵に対応した秘密鍵とトークンをセット。(自サイトの公開鍵を使用して決済を試みている事をチェックする)
  5. stripe.Charge.createが実行。決済処理が実行される。

ビュー

from django.shortcuts import render,redirect

from django.views import View
from .models import Topic

import stripe
from django.conf import settings

stripe.api_key = settings.STRIPE_API_KEY

class IndexView(View):

    def get(self, request, *args, **kwargs):
        context = {}

        context["topics"]   = Topic.objects.all()


        #顧客がチェックアウトセッションを作るようにHTML側で仕立てる
        context['data_key'] = settings.STRIPE_PUBLISHABLE_KEY
        context['data_amount']      = 30000 
        context['data_name']        = "テスト決済"
        context['data_description'] = "ご注文を決済します"
        context['data_currency'] =  'JPY'

        return render(request,"bbs/index.html",context)

    def post(self, request, *args, **kwargs):

        posted  = Topic( comment = request.POST["comment"] )
        posted.save()

        return redirect("bbs:index")

index   = IndexView.as_view()


class CheckoutView(View):

    def post(self, request, *args, **kwargs):
        stripe.api_key = settings.STRIPE_API_KEY
        token = request.POST['stripeToken']

        #決済処理
        try:
            #トークンを元に決済を実行する
            charge = stripe.Charge.create(
                amount= 30000,
                currency='JPY',
                source=token,
                description='テスト決済完了',
            )
            context = { "charge":charge }

            print("決済完了")

        except stripe.error.CardError as e:

            #決済が失敗した場合の処理
            print("失敗しました。")

        return redirect("bbs:index")


checkout    = CheckoutView.as_view()

テンプレート

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
	<title>簡易掲示板</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

</head>
<body>

    <main class="container">
        <form method="POST">
            {% csrf_token %}
            <textarea class="form-control" name="comment"></textarea>
            <input type="submit" value="送信">
        </form>

        {% for topic in topics %}
        <div class="border">
            {{ topic.comment }}
        </div>
        {% endfor %}

        <form action="{% url 'bbs:checkout' %}" method="POST">
            {% csrf_token %}
            <script
                src="https://checkout.stripe.com/checkout.js" class="stripe-button"
                data-key="{{ data_key }}"
                data-amount="{{ data_amount }}"
                data-name="{{ data_name }}"
                data-currency="{{ data_currency }}"
                data-description="{{ data_description }}"
                data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
                data-locale="auto">
            </script>
            <button>決済</button>
        </form>

    </main>
</body>
</html>

参照元

ソースコード

https://github.com/seiya0723/django-stripe

スポンサーリンク

シェアボタン

Twitter LINEで送る Facebook はてなブログ