オンライン決済サービスというと、審査が厳しいとか実装が難しいとか何かとハードルが高い印象がある。また、いくつかのオンライン決済サービスがあり、一長一短があるため選ぶのも難しい。
色々悩んだ結果、オンライン決済サービスの1つであるStripeというサービスを使ったところ、非常に良かったので、この記事ではStripeについて紹介する。
また、自身のメモのためにもDjangoでの実装について軽く紹介する。
オンライン決済サービスについて
オンライン決済サービスはとてもたくさんのサービスがあり、手数料の安さや決済手段の多さ、サービスの手厚さなどでそれぞれが差別化を図っている。どのオンライン決済が良いかは自分自身のサービスに合わせて決めると良い。
具体的に挙げると下のようなサービスがある。
- Stripe
- 今回紹介するのがこれ。
- Square
- オフライン決済もやっている。お店にいくとSquareの端末をたまに見る。
- Pay.jp
- 日本発のサービス。VISAやMasterCardの手数料が3.0%と安い。
Stripeについて
Stripeは2021年8月時点で米国最大のユニコーン企業である。IPOしておらず時価総額が10兆円を超えている。
オンラインサービスを手掛ける企業を中心とした顧客を持ち、Shopify, Slack, Zoom, Lyftなど名だたる企業が利用している。
Stripeを利用する上でメリット・デメリットをまとめると以下のようになる。
メリット
- 決済手数料が一律3.6%でシンプル
- 他のサービスはJCBだけ3.95%だったりと複雑になっていることが多い。
- 振込手数料や初期費用などその他の手数料がゼロ
- 実装が非常に簡単
- ドキュメントも充実している
- クレジットカードだけではなく、Google PayとApple Payにもデフォルトで対応。
- セキュリティが強固
- UIUXが考えつくされていて、WebコンソールもAPIもとても使いやすい
- 審査が後からなのですぐに使い始められる
- 個人事業主でも使える
- C2Cサービスのような複雑な決済にも対応
デメリット
- 一律3.6%という手数料が必ずしも安いわけではない
Djangoでの実装
Stripeではドキュメントが非常に丁寧に作られているため、基本的にはそのドキュメントを見るだけで決済を実装することができる。
ただ、DjangoではなくFlaskでの実装が書かれていることが多く、適宜読み替える必要がある。
ここではDjangoで支払いを受け付ける方法から、支払いの処理をするためのWebhookの使い方までをメモしておく。(アカウントの作成は済んでいるものとする。)
基本的には以下のドキュメントをDjango版に置き換え、少しアレンジしたものである。
支払いを受け付ける
支払いページの作成はStripe APIのおかげで非常簡単に実装することができる。
まずはStripeライブラリをインストールする。
pip intall stripe
StripeのマイページからAPIキーを取得し、settings.py
に記述する。(または、.env
ファイルに記述する。)
STRIPE_PUBLIC_KEY = 'YOUR_PUBLIC_KEY'
STRIPE_SECRET_KEY = 'YOUR_SECRET_KEY'
例として、myapp/models.py
にProductテーブルを作り、すでに商品がいくつか登録されている状態であるとする。
また、注文はOrderテーブルに記録する。(stripeカラムについては後ほど説明する。)
from django.db import models
class Product(models.Model):
name = models.CharField(verbose_name='商品名', max_length=100)
description = models.TextField(verbose_name='説明')
price = models.PositiveIntegerField(verbose_name='価格')
timestamp = models.DateTimeField(verbose_name='登録日', auto_now_add=True)
thumbnail = models.ImageField(upload_to='images/', verbose_name='画像')
class Order(models.Model):
# customer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
price = models.PositiveIntegerField(verbose_name='価格')
timestamp = models.DateTimeField(verbose_name='発注日', auto_now_add=True)
stripe = models.CharField(verbose_name='Stripe Session', max_length=100)
myapp/templates/order.html
に以下のようなhtmlファイルを作成する。
決済に進むボタンを押すと、チェックした商品の決済ページ(Stripeのページ)に移動する。
<html>
<head>
<title>商品の購入</title>
</head>
<body>
<form action="{% url 'create-checkout-session' %}" method="POST">
{% comment %} ユーザ情報や住所の収集なども行えると良い {% endcomment %}
{% for p in products %}
<p>
<input type="radio" name="product" value="{{ p.id }}" id="id_product_{{ p.id }}">
<label for="id_product_{{ p.id }}">{{ p.name }}</label>
</p>
{% endfor %}
<button type="submit">決済に進む</button>
</form>
</body>
</html>
また、myapp/templates/success.html
に以下のようなhtmlファイルを作成する。
商品の購入が完了したあとに移動するページである。
<html>
<head><title>Thanks for your order!</title></head>
<body>
<h1>Thanks for your order!</h1>
<p>
We appreciate your business!
If you have any questions, please email
<a href="mailto:[email protected]">[email protected]</a>.
</p>
</body>
</html>
次に、myapp/views.py
に次のように記述。
import stripe
from .models import *
from django.views.decorators.http import require_POST
from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse
from django.conf import settings
stripe.api_key = settings.STRIPE_SECRET_KEY
def order_view(request):
products = Product.objects.all()
return render(request, 'order.html', {'products': products})
def checkout-success-view(request):
return render(request, 'success.html')
@require_POST
def create_checkout_session(request):
product_id = request.POST['product']
product = get_object_or_404(Product, id=product_id)
# customer = request.user
session = stripe.checkout.Session.create(
# customer_email= customer.email,
payment_method_types=['card'],
line_items=[{
'price_data': {
'currency': 'jpy',
'product_data': {
'name': product.name,
'images': [product.image.url]
},
'unit_amount': product.price,
},
'quantity': 1,
}],
mode='payment',
success_url=request.scheme + '://' + request.get_host() + reverse('success'),
cancel_url=request.scheme + '://' + request.get_host() + reverse('order'),
metadata = {
'product_id': product.id,
}
)
return redirect(session.url)
重要なのがstripe.checkout.Session.create
という部分。
商品のデータをここで指定できる。(事前に商品データをStripe上に登録することもできる。)
また、自身のデータベースに保存するためにproduct.id
をmetadata
に入れておくのが良い。session.url
にリダイレクトすることでStripeの購入ページに移動することができる。
詳しくは、公式ドキュメントを参照。ドキュメントは熟読するのがおすすめ。
また、上では@require_POST
デコレータを使ってcreate_checkout_session
ではPOSTのみを受け付けるようにしている。このデコレータについて。
次に、myapp/url.py
に次のように記述。
from django.urls import path
from . import views
urlpatterns = [
path('order', views.order-view, name='order'),
path('create-checkout-session', views.create-checkout-session, name='create-checkout-session'),
path('checkout/success', views.checkout-success-view, name='success'),
]
以上で、テストカードとして、4242 4242 4242 4242
など(有効期限とセキュリティコードは任意)を入力すれば、決済が完了したことが分かる。
注文の処理(Webhookの利用)
決済が完了すると、上のstripe.checkout.Session.create
という部分で記載したsuccess_url
に移動する。
このページのViews関数でOrderの処理(データベースに保存、購入者にメールなど)を行うこともできるが、非推奨となっている。
これは、必ずしもユーザが注文完了後にsuccess_url
に到達するとは限らないためである。
そこでWebhookを利用してStripeから注文が完了した通知を受け取り、注文の処理をする。
そもそもWebhookとは、あるイベントが発生したことを指定したURLに通知する仕組みである。
今回は、決済の完了を指定のURLに通知させ、views関数で処理を行う。
まず、Stripe CLIをインストールする。
Stripe CLIはローカルでStripeの組み込みをテストするものである。
インストールしたら次にようにイベントを指定にURLに転送できるようにしておく。
stripe listen --forward-to localhost:4242/webhook
また、出てきたendpoint_secret
をsettings.py
などに書いておく。
Dockerでインストールした場合、次の記事に書いたことを注意する。
次に、myapp/views.py
に次のように追記する。
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
endpoint_secret = settings.ENDPOINT_SECRET
@csrf_exempt
@require_POST
def checkout-success-webhook(request):
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
event = None
try:
event = stripe.Webhook.construct_event(
payload, sig_header, endpoint_secret
)
except ValueError as e:
# Invalid payload
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError as e:
# Invalid signature
return HttpResponse(status=400)
# Handle the checkout.session.completed event
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
# Fulfill the purchase...
fulfill_order(session)
# Passed signature verification
return HttpResponse(status=200)
def fulfill_order(session):
order = Order.object.create(
product = Product.objects.get(id=session['metadata']['product_id']),
price = session['amount_total'],
stripe = session['id']
)
print("Fulfilling order")
@csrf_exempt
はDjangoのCSRF認証を無視するものである。このデコレータについてはこちら。
イベントハンドラには誰でもデータを書き込むことができるため、event
が Stripe から送信されていることを確認している。
fulfill_order
関数の中で実際の処理を書いている。Order
テーブルの中にstripe
カラムを作ったのは、stripeの決済情報と紐付けるためである。
stripe.checkout.Session.retrieve(order.stripe)
などで、決済情報を参照することができる。
また、myapp/urls.py
に次のように書き込む。
urlpatterns = [
path('webhook', views.checkout-success-webhook, name='checkout-success-webhook'),
]
これでStripe CLIを動かした状態で、テストカードで商品を購入すると、処理が完了していることが分かる。
おわりに
DjangoでStripeの決済を実装する方法を紹介した。
この記事での実装は導入のほんの一部で、実際に決済を実装するとなると細かな仕様(割引、配送料金、税金などなど)を詰める必要がある。
Stripeはカスタマイズ性に非常に優れているので、ドキュメントを検索すればそれっぽいことが出てくる。
ドキュメントさえ読めば、本当に簡単に実装できてしまうのがStripeのすごさである。
C2Cのような複雑な資金移動をする場合に使う、Stripe Connectについても別記事でまとめる。
コメント