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

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

Python - Django
2018年11月23日2:19に更新(約20日前)
2018年11月7日16:45に作成(約36日前)

旧ブログ移行記事です。

概要

今回はDjangoStripeという決済サービスを使い、ちょっとしたショッピングサイトを作ります。

準備

まずですが、Stripeを利用するためにユーザー登録しましょう。

登録が済んだら、ダッシュボードへいきましょう。開発者APIキーと開き、テスト用キーを表示ボタンを押し、2つのキーを表示しておきます。 ダッシュボードの画面

公開可能と、シークレットキーの2つですね。本番環境利用の申請をするまでは、テスト用のキーが表示されています。

Djangoのsettings.pyにて、以下のように定義しておきます。

STRIPE_PUBLIC_KEY = 'あなたの公開可能キー'
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/book_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/book_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-localeautoでも大丈夫だと思いますが、念の為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...は、テスト用のカードです。

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

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

Django側のBuyingHistoryも見てみましょう。

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

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

参考リンク

PHPでの解りやすいサンプル
stripeのGithub
API
APIの日本語訳

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

記事にコメントする