25
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Stripe決済のDjangoでの実装

Last updated at Posted at 2020-03-22

初めに

Stripeを使えば比較的簡単に決済機能を実装できます。この記事ではStripeが用意した決済フォームにページをリダイレクトさせることで、自サイトではクレジットカード情報を保持することなく支払い機能を自サイトに組み込むことができます。

基本はStripeのドキュメントGithub上のサンプルを基にして実装しています。また、Djangoで実装します。正直、試行錯誤しながら適当に作ったので実装に関して突っ込みどころ満載です。

最終的にはStripeのサンドボックスの劣化版が実装されます。また、以下の情報は2020年3月時点のものです。

環境設定

  • Python==3.8.1
  • Django==3.0.4
  • stripe==2.43.0
  • django-environ==0.4.5

Step0: 前準備

適当にディレクトリを作成する。

mkdir stripe-test
cd stripe-test

pipenvを使って仮想環境を構築します。

pipenv --python 3.8
pipenv install django stripe django-environ

pipenv shellで仮想環境を起動する。pipenvについてさらに詳しく知りたい場合はPipenvを使ったPython開発まとめを参照。

# shell起動
pipenv shell

Djangoプロジェクトを作成する。

django-admin startproject stripe_test .
django-admin startapp checkout

Stripeにアカウント登録

Stripeにアカウント登録する。アカウント作成やダッシュボードの見方についてはRailsのdeviseとStripe(とBootstrap)の組み合わせで、ただただユーザー登録させたユーザーに定期更新購読させるだけのappを作りました②に詳しい。

公開ビジネス情報を入力する。2020年3月現在だと以下のURL。

次に開発用の公開キーとシークレットキーを確認する。2020年3月現在だと以下のURL。

Django Setting

Djangoのsettingを以下のようにする。.envファイルを取り込むのにdjango-environを使用している。

stripe_test/stripe_test/settings.py

import os
import environ

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_NAME = os.path.basename(BASE_DIR)

# django-environから環境変数を読み込む
env = environ.Env()
env.read_env(os.path.join(BASE_DIR, '.env'))

### 省略 ###

INSTALLED_APPS = [
    ...,
    'stripe',#追加
    'checkout.apps.CheckoutConfig', # 自作 チェックアウト
]

### 省略 ###

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',
            ],
        },
    },
]

### 省略 ###
# Static files (CSS, JavaScript, Images)

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

STRIPE_SECRET_KEY = env('STRIPE_SECRET_KEY')
STRIPE_PUBLISHABLE_KEY = env('STRIPE_PUBLISHABLE_KEY')

.envには例えばこういう設定を行う。使用通貨がアメリカドル(usd)の場合はセント単位で計算するので1234と入力した場合は$12.34となる。

.env
# Stripe API keys
STRIPE_PUBLISHABLE_KEY=pk_12345
STRIPE_SECRET_KEY=sk_12345

# Checkout settings 
DOMAIN=http://localhost:8000/
BASE_PRICE=1099
CURRENCY=usd

Step1:サーバー側でチェックアウト・セッションを実装する

セッションの作成はサーバー側で行う。Python + Django でJSONの送受信を参考にして、以下のようにDjangoを使って、Checkout Sessionを作成する。

stripe_test/checkout/views.py
# -*- coding: utf-8 -*-
import logging
import os
import json

from django.shortcuts import render
from django.conf import settings
from django.views import generic

import stripe
from django.http.response import JsonResponse
from django.views.decorators.csrf import csrf_exempt

logger = logging.getLogger(__name__)

@csrf_exempt
def onetime_payment_checkout(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        domain_url = os.getenv('DOMAIN')
        stripe.api_key = settings.STRIPE_SECRET_KEY
        try:
            # Create new Checkout Session for the order
            # ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
            checkout_session = stripe.checkout.Session.create(
                success_url=domain_url +
                "checkout/success?session_id={CHECKOUT_SESSION_ID}",
                cancel_url=domain_url + "checkout/canceled/",
                payment_method_types=["card"],
                line_items=[
                    {
                        "name": "Pasha photo",
                        "images": ["https://picsum.photos/300/300?random=4"],
                        "quantity": data['quantity'],
                        "currency": os.getenv('CURRENCY'),
                        "amount": os.getenv('BASE_PRICE'),
                    }
                ]
            )
            logger.debug( str(checkout_session))
            return JsonResponse({'sessionId': checkout_session['id']})
        except Exception as e:
            logger.warning( str(e) )
            return JsonResponse({'error':str(e)})

Step2:チェックアウトにリダイレクト

基本的にJavascriptを使って実装していく。実現していることはstripe.jsを使ってStripe側にデータを渡して、Stripeで決済してもらった後に自サイトに戻る。

stripe_test/templates/base.html
{% load i18n static %}
<!DOCTYPE html>{% get_current_language as LANGUAGE_CODE %}
<html lang="{{ LANGUAGE_CODE|default:"en-us" }}">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
        <title>{% block title %}{% endblock %}</title>
        <script src="{% static 'js/jquery-3.3.1.slim.min.js' %}"></script>
        <script src="{% static 'js/bootstrap.min.js' %}"></script>
        {% block additional_script %}
        {% endblock %}
    </head>
    <body>
        <div class="container">
            {% block content %}
            {% endblock %}
        </div>
    </body>
</html>
stripe_test/templates/checkout/checkout_test.html
{% extends "base.html" %}

{% load i18n static %}
{% block title %}Checkout_test{% endblock title %}

{% block additional_script %}
<script src="https://js.stripe.com/v3/"></script>
<script src="{% static 'checkout/js/script.js' %}"></script>
{% endblock additional_script%}


{% block content %}
<div>
    <h1 data-i18n="headline"></h1>
    <h4 data-i18n="subline"></h4>
    <div class="pasha-image">
      <img
        src="https://picsum.photos/280/320?random=4"
        width="140"
        height="160"
      />
    </div>
  </div>
  <div class="quantity-setter">
    <input type="number" id="quantity-input" min="1" max="2" value="1" />
  </div>
  <p class="sr-legal-text" data-i18n="sr-legal-text"></p>

  {% csrf_token %}
  <button
    id="submit"
  >PAYMENT</button>
</section>
<div id="error-message"></div>
</div>
{% endblock content %}

自作Scriptは以下のサイトを参考にした

stripe_test/static/checkout/js/script.js
/* Handle any errors returns from Checkout  */
var handleResult = function(result) {
    if (result.error) {
      var displayError = document.getElementById("error-message");
      displayError.textContent = result.error.message;
    }
  };

  // Create a Checkout Session with the selected quantity
  var createCheckoutSession = function() {
    var inputEl = document.getElementById("quantity-input");
    var quantity = parseInt(inputEl.value);
    return fetch("/checkout/create-checkout-session/", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        quantity: quantity,
      })
    }).then(function(result) {
      return result.json();
    });
  };

  /* Get your Stripe publishable key to initialize Stripe.js */
  fetch("/checkout/config")
    .then(function(result) {
      return result.json();
    })
    .then(function(json) {
      window.config = json;
      var stripe = Stripe(config.publicKey);
      // Setup event handler to create a Checkout Session on submit
      document.querySelector("#submit").addEventListener("click", function(evt) {
        createCheckoutSession().then(function(data) {
          stripe
            .redirectToCheckout({
              sessionId: data.sessionId
            })
            .then(handleResult);
        });
      });
    });

HTMLとJavascriptが実装できたら、checkout_test.htmlが見られるようにviews.pyを以下を追加する。

stripe_test/checkout/views.py
class IndexView(generic.TemplateView):
    template_name = "checkout/checkout_test.html"
    def get(self, request, *args, **kwargs):
        return render(request, self.template_name )

とりあえずはcheckoutアプリのマッピング情報を記述して、最初のページだけ見られるようにする。これはcheckout/urls.pyを以下のように編集することで実現できる。

stripe_test/checkout/urls.py
from . import views
from django.urls import path,include

app_name = 'checkout'

urlpatterns = [
    path('',views.IndexView.as_view(), name="index"),
]

Djangoアプリではない、ホームページ側のurls.py(この記事ではstripe_test/stripe_test/urls.py)を以下のように修正する。

stripe_test/stripe_test/urls.py
from django.contrib import admin
from django.urls import path,include
from django.views.generic import RedirectView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', RedirectView.as_view(url='/checkout')),
    path('checkout/', include('checkout.urls')),
]

上記自作のjavascript(stripe_test/static/checkout/js/script.js)ではfetch("/checkout/config")を行っているが、これはjsonでstripeAPIの公開キーを受け取っている。サーバー側の実装は以下のとおり。

stripe_test/checkout/views.py
@csrf_exempt
def stripe_config(request):
    if request.method == 'GET':
        stripe_config = {
            'publicKey': settings.STRIPE_PUBLISHABLE_KEY,
            'basePrice': os.getenv('BASE_PRICE'),
            'currency': os.getenv('CURRENCY'),
        }
        return JsonResponse(stripe_config, safe=False)
stripe_test/checkout/urls.py
from . import views
from django.urls import path,include

urlpatterns = [
    ...,
    path('config/',views.stripe_config),
    ...,
]

python manage.py runserverでとりあえずサイトを起動してhttp://localhost:8000/にアクセスすると以下の感じの画面が出るはずだ。

サンプルページ.png

ただ、これだとPAYMENTを押しても、何も起きないはず。なぜなら、ボタン押されたあとの実装を行っていないから。具体的には自作javascriptのcreateCheckoutSession関数はとあるURLにPOSTしているが、Django側で設定していないので、設定を行う必要がある。それにはurls.pycreate-checkout-sessionを追加すればいい。

stripe_test/checkout/urls.py

urlpatterns = [
    ...,
    path('create-checkout-session/',views.onetime_payment_checkout , name='create-checkout'),
    ...,
]

以上まで実装を行えば、Stripe側にURLが飛んで決済情報を入力する画面が表示されるはずです(参考:Stripe Checkout)。後は、決済情報を入力し終わったリダイレクト先のページ(success.htmlcanceled.html)を実装していけば、一通り決済はできるようになっている。

#テスト
テスト用の決済カードは複数用意されているので、適宜選ぶ。特にこだわりがないのならば 4242424242424242を使用。

もし、うまく実装できていればダッシュボード上に支払いが反映されているので、確認してみてください。

参考:Github

上記の実装はGithubに公開しているので、参考にしてください。

参考サイト

25
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?