naritoブログ

このブログはPython/DjangoとBootstrap4で作成されました
Githubのソースコード

Djangoで、簡単なショッピングサイトを作る(stripe決済)

約132日前 2018年1月14日16:42
プログラミング関連
Django Python
今回はDjangoとstripeという決済サービスを使い、ちょっとしたショッピングサイトを作ります。
まずですが、stripeを利用するためにユーザー登録しましょう。
https://stripe.com/jp

登録が済んだら、ダッシュボードへいきましょう。「API」の画面を開き、「テスト用キーを表示」ボタンを押し、2つのキーを表示しておきます。公開可能と、シークレットキーの2つですね。本番環境利用の申請をするまでは、テスト用のキーが表示されています。



settings.pyにて、以下のように定義しておきます。
STRIPE_PUBLIC_KEY = 'pk_test_tY07oL78EYGhMl3ZyTTUhOR4'
STRIPE_SECRET_KEY = 'こっちはシークレットキー'



stripeを利用するための、Pythonライブラリがあります。pipでインストールしておきましょう。
pip install stripe


私は本が好きなので、オンライン書店をすることにしました。まず、models.pyを以下のようにします。
from django.conf import settings
from django.db import models
from django.utils import timezone


class Book(models.Model):
    """本"""

    title = models.CharField('タイトル', max_length=200)
    price = models.IntegerField(default=1000)
    description = models.TextField('説明')
    created_at = models.DateTimeField('日付', default=timezone.now)

    def __str__(self):
        return self.title


class BuyingHistory(models.Model):
    """購入履歴"""

    book = models.ForeignKey(Book, verbose_name='購入書籍', on_delete=models.PROTECT)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='購入ユーザー', on_delete=models.PROTECT)
    is_sended = models.BooleanField('発送フラグ', default=False)
    stripe_id = models.CharField('タイトル', max_length=200)
    created_at = models.DateTimeField('日付', default=timezone.now)

    def __str__(self):
        return '{} {} {}'.format(self.book, self.user.email, self.is_sended)




Bookモデルは簡単ですね。タイトルと値段、本の説明等のフィールドを持ちます。stripeでは日本円での決済ができるので単純なIntegerFieldにしましたが、使用通貨によっては小数点部分が必要になりますので、その際はDecimalField等を使いましょう。

BuyingHistoryは、ユーザーの購入履歴です。購入書籍、購入ユーザー、発送済みフラグ、そしてstripe_idというフィールドを持たせています。stripeには豊富なAPIや解りやすい管理画面があるので、そちらでも履歴は確認できますが、Django側にも履歴を残したかったので作成したモデルです。


views.pyです。
from django.conf import settings
from django.shortcuts import redirect, render
from django.views import generic
from .models import Book, BuyingHistory

import stripe
stripe.api_key = settings.STRIPE_SECRET_KEY


class IndexView(generic.ListView):
    model = Book


class DetailView(generic.DetailView):
    model = Book

    def post(self, request, *args, **kwargs):
        """購入時の処理"""
        book = self.get_object()
        token = request.POST['stripeToken']  # フォームでのサブミット後に自動で作られる
        try:
            # 購入処理
            charge = stripe.Charge.create(
                amount=book.price,
                currency='jpy',
                source=token,
                description='メール:{} 書籍名:{}'.format(request.user.email, book.title),
            )
        except stripe.error.CardError as e:
            # カード決済が上手く行かなかった(限度額超えとか)ので、メッセージと一緒に再度ページ表示
            context = self.get_context_data()
            context['message'] = 'Your payment cannot be completed. The card has been declined.'
            return render(request, 'app/post_detail.html', context)
        else:
            # 上手く購入できた。Django側にも購入履歴を入れておく
            BuyingHistory.objects.create(book=book, user=request.user, stripe_id=charge.id)
            return redirect('app:index')

    def get_context_data(self, **kwargs):
        """STRIPE_PUBLIC_KEYを渡したいだけ"""
        context = super().get_context_data(**kwargs)
        context['publick_key'] = settings.STRIPE_PUBLIC_KEY
        return context




stripeの使い方としては、stripe.api_keyにシークレットキーを設定し、各種APIを叩いていくことになります。ここはおまじないと思ってしまってください。
import stripe
stripe.api_key = settings.STRIPE_SECRET_KEY



書籍の一覧を表示するビューです。特に言うことはないですね。
class IndexView(generic.ListView):
    model = Book


book_list.htmlは、以下のようになります。これも言うことはありません。一覧を表示して、詳細ページへのリンクを作るだけです。
{% extends 'app/base.html' %}

{% block content %}
<h1>書籍一覧</h1>
{% for book in book_list %}
  <p><a href="{% url 'app:detail' book.pk %}">{{ book.title }}</a></p>
{% endfor %}
{% endblock %}



詳細ページのビューは少し複雑になったので、機能毎に説明していきます。まず、書籍の詳細を表示する部分です。
PUBLIC_KEYをテンプレートに渡す必要がありまして、get_context_dataメソッドを上書きして渡すことにしています。context_processorsを使ったり、テンプレートにPUBLIC_KEYを直接書いてもいいかもしれません。
class DetailView(generic.DetailView):
    model = Book

    def get_context_data(self, **kwargs):
        """STRIPE_PUBLIC_KEYを渡したいだけ"""
        context = super().get_context_data(**kwargs)
        context['publick_key'] = settings.STRIPE_PUBLIC_KEY
        return context



そして、購入処理もこの詳細表示ビューで行います。もちろん、専用のビューを作っても良いでしょう。処理内容はコメントのとおりです。
    def post(self, request, *args, **kwargs):
        """購入時の処理"""
        book = self.get_object()
        token = request.POST['stripeToken']  # フォームでのサブミット後に自動で作られる
        try:
            # 購入処理
            charge = stripe.Charge.create(
                amount=book.price,  # 値段
                currency='jpy',  # 日本円
                source=token,
                description='メール:{} 書籍名:{}'.format(request.user.email, book.title),
            )
        except stripe.error.CardError as e:
            # カード決済が上手く行かなかった(限度額超えとか)ので、メッセージと一緒に再度ページ表示
            context = self.get_context_data()
            context['message'] = 'Your payment cannot be completed. The card has been declined.'
            return render(request, 'app/post_detail.html', context)
        else:
            # 上手く購入できた。Django側にも購入履歴を入れておく
            BuyingHistory.objects.create(book=book, user=request.user, stripe_id=charge.id)
            return redirect('app:index')



book_detail.htmlは、以下のようにしました。ログインしていないと、購入用のボタンを見えなくしています。
{% extends 'app/base.html' %}

{% block content %}
<p>{{ message }}</p>
<a  href="{% url 'app:index' %}">戻る</a>

<!-- 書籍の情報の表示 -->
<h1>{{ book.title }}</h1>
<p class="lead">{{ book.created_at }} - {{ book.price }}円</p>
<p>{{ book.description | linebreaksbr }}<p>

<!-- 購入ボタン・フォームの作成 -->
{% if user.is_authenticated %}
<form action="" method="POST">
  <script
      src="https://checkout.stripe.com/checkout.js" class="stripe-button"
        data-key="{{ publick_key }}"
      data-amount="{{ book.price }}"
      data-name="なりと書店"
      data-description="{{ book.title }}"
      data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
      data-locale="ja"
      data-currency="jpy"
      data-email="{{ user.email }}">
  </script>
  {% csrf_token %}
</form>
{% endif %}
  
{% endblock %}



data-localeは「auto」でも大丈夫だと思いますが、念の為jaとしました。data-currencyも円にし、今回のようにユーザーのメールアドレスがわかっている場合はdata-email属性に指定します。わからなければ、data-email属性は消しましょう。
  <script
      src="https://checkout.stripe.com/checkout.js" class="stripe-button"
      data-key="{{ publick_key }}"
      data-amount="{{ book.price }}"
      data-name="なりと書店"
      data-description="{{ book.title }}"
      data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
      data-locale="ja"
      data-currency="jpy"
      data-email="{{ user.email }}">
  </script>



では、実際に動くか試していきます。本番ではhttpsからのアクセスでないと弾かれるようですが、登録した後のテスト状態ではhttpでも大丈夫なようです。
まずトップページ


詳細ページに行くと、Pay with Card というボタンができていますね。


クリックして、決済情報を入力します。この4242...は、テスト用のカードです。https://stripe.com/docs/testing#cards に他にもテスト用のカードデータが配布されています。


購入が終わったら、stripeの管理画面に行きます。「支払い」をクリックすると、購入されたものが表示されます。


金額や説明を確認できます。



Django側のBuyingHistoryも見てみましょう。購入ユーザーや購入書籍、stripe_id(タイトルと表示されちゃってますね...)を上手く作れました。stripe_idはDetailViewのpostメソッド内にて、送信後にcharge.idとして取得したもので、stripeの管理画面で表示されたIDと同じものになります。これを格納しておくと、Django⇔stripe間でのデータの紐付けもできるでしょう。


ユーザーのメールアドレスを元に受け渡しの連絡を行ったり、住所となるフィールドを持たせた拡張Userモデル等を作れば発送もできるようになるでしょうし、電子コンテンツの場合は購入済みならダウンロードさせる、といったことができそうです。

参考
PHPでの解りやすいサンプル:https://qiita.com/4_R_R_S/items/c9d8e949c9d9b2fcd0d7

公式ドキュメントの類
https://github.com/stripe/stripe-python
https://stripe.com/docs/api/python
https://stripe.com/docs/checkout

APIの日本語訳:https://qiita.com/koki1023/items/dd4310de9888efe278a9