naritoブログ

【お知らせ】
新ブログができました。今後そちらで更新し、このサイトは更新されません(ウェブサイト自体は残しておきます)
このブログの内容に関してコメントしたい場合は、新ブログのフリースペースに書き込んでください

このブログの内容を新ブログに移行中です。このブログで見つからない記事は、新ブログにありま

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

約274日前 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