この投稿は 「Django Advent Calendar 2016 - Qiita」 の 4日目の記事です。
今日は クリスマス Advent Calendar の最終日ですが、4日目の記事を書いています。
というのも、自分が担当した 12/5 の「ベスト・オブ・Django本! - akiyoko blog」の前日だけ何故かずーっと空いていて、気持ち悪かったので。。
この記事では、「Django で Stripe 決済」を実装・検証します。
Django Advent Calendar 22日目に書いた「Django と Paypal と私(主に PayPal 決済の最新事情について) - akiyoko blog」のスピンオフ的な位置付けです。
<過去記事>
akiyoko.hatenablog.jp
Stripe とは
まず、「Stripe とは何ぞや?」から説明します。
Stripe は、2016年10月に日本で本番サービスイン した、新しい決済代行サービスです。後発サービスだけに、導入が簡単だったり、API が洗練されていて使いやすかったり、管理画面(ダッシュボード)がスッキリしていて直感的に分かりやすかったりと、なかなかイケてるサービスなのです。
なお、決済サービスの接続方式は「直接決済方式」で、他のサイトへの画面遷移を伴わず、離脱が少ない決済フローが実現できるのも特徴です。 *1
本社は米国カリフォルニア州サンフランシスコで、立ち上げが 2011年という比較的新しいスタートアップですが、今や世界中で数千社、年間数十億ドルの支払いを処理しており、飛ぶ鳥を落とす勢いの一大企業にまで成長しています。 *2
現時点で 世界25ヶ国でサービスを展開 しており、日本企業であれば、世界中のあらゆる国のエンドユーザから利用してもらうことが可能です。ちなみに私は、これを「世界25ヶ国でしか使えない」と勘違いしていたのですが、気になって問い合わせてみたところ、
「25 ヶ国に展開」という意味につきましては、Stripeのアカウントを作成し、オンラインで決済を受けることができる国数が25か国、という意味となります。
御社のサービス・プロダクトを購入されるエンドユーザに関しては、国の制限なく利用可能です。御社のターゲットとなるお客様に合わせた通貨を指定することで、その通貨でエンドユーザは決済を行うことができ、購入体験を損なうことなく決済が完結いたします。そして、日本以外の通貨でお支払いを受けた際には、Stripeが自動で円換算を行い、御社の銀行口座へ毎週お振込みいたします。
という丁寧な返事がすぐに返ってきました。
私の勘違いで危うく Stripe を候補から外してしまうところでした。。 念のため問い合わせしておいて良かったぁ。俺、グッジョブ!!
なお、私が必要としている決済サービスとしては、
- 海外からの決済が可能(JPY以外の通貨が扱える) *3
というのを最低条件としていて、調査前は PayPal を第一候補、Stripe を第二候補と考えていたのですが、実際に両者を触って検討してみた現在の率直な感想では、Stripe の方が使い勝手が良さそうな印象です。
PayPal の最新事情については、以下の過去記事を参考にしてください。
<過去記事>
akiyoko.hatenablog.jp
Stripe については、以下の公式サイトのほか、
以下のサイトを参考にしました(本番サービスイン前の少し古い情報もあるのでご注意を)。
Stripe のメリットとデメリットは?
私が思いつく限りの、Stripe のメリット(プラス面)とデメリット(マイナス面)を挙げてみます。
メリット(プラス面)
- 固定費用なし(決済成立ごとに手数料 3.6%) *4
- 導入(≒実装)がめちゃくちゃ簡単
- 直接決済方式(画面遷移がない)のため、離脱が少ない
- クレジットカード情報の入力フォームを用意しなくても、checkout.js や stripe.js でセキュア(PCI-DSS に準拠)なフォームを簡単に作成できる
- 管理画面(ダッシュボード)が見やすい
- 三井住友カードが全面バックアップしている(らしい)ので安心 *6
- 売上金の円建て振り込みが可能(手数料は週一回の振り込みまで無料)
- 定期支払い(継続課金)も利用可能
- Stripe Radar, CVC, ZIP code などによる不正使用対策が充実
デメリット(マイナス面)
- 現時点で JCB カードが利用不可 *7, *8
- 銀行振込み・コンビニ支払いは利用不可(クレジットカードのみ)
現状、大きなデメリットは JCBカードが使えないというほぼ一点のみかと思われます。
それを許容できるのであれば、Stripe は決済サービスの筆頭候補になり得るはずです。
(参考)
Stripe 決済の仕組みは?
Stripe が推奨しているオンライン即時決済フローは、下図のようなものになります。
① ショッピングカート画面(「Stripe で支払う」ボタン)
↓
② 決済モーダルウィンドウ(「支払う」ボタン)
↓
③ 決済完了画面
すごくシンプルですよね。
Stripe 決済の接続方式は「直接決済方式」で、Stripe サイト等にリダイレクトされることなく決済を完了させることができます。つまり、エンドユーザが「Stripe」を意識することがないので、ユーザの離脱防止に効果が期待できます。
次に、Stripe がユーザのクレジット情報を処理するまでの詳細な仕組みについて、順を追って説明します。
Step 1:Securely collecting payment information
(参考)Card Payments Quickstart
- フォームに Stripe 謹製の「checkout.js」を設置しておく。
- ユーザがボタンをクリックすると、クレジットカード(およびその他の)情報を入力するためのモーダルウィンドウが立ち上がる。
- モーダルウィンドウ内でクレジットカード情報を入力して決済ボタンをクリックすると、checkout.js が裏側で Stripe の API とやり取りをおこない、数分間だけ有効になるトークンを生成してから、自サーバのアクションに POSTリクエストを実行する(トークンを「stripeToken」というパラメータで送信)。
Step 2:Creating Charges
(参考)Card Payments Quickstart
- 自サーバ側でトークンを受け取り、Stripe の API を利用して決済処理を実行する(トークンも渡す)
たったこれだけ。
フォームに埋め込むスクリプトもほんの数行だけですし、サーバ側の処理もリクエストからトークンを受け取って Stripe API を使って即時決済を実行するだけです。
ユーザが入力したクレジットカード情報は、Stripe API によって自動的にトークンとして自前に暗号化され、自サーバ側には流れてこないので安全 です。ただし、Stripe API とのやり取りをセキュアに保つためには、フォームを置くサイトを SSL化しておく必要があります。
なお、Step 1 でトークンを生成する手段は、
の 3種類が用意されており、ECサイトのオンライン決済では上の二つのいずれかを利用すればよいでしょう。
なお、checkout.js のパラメータをいろいろとカスタマイズできるようになっているので、ほとんどの場合は標準フォーム(checkout.js)の方で事足りるのではないでしょうか。
(参考)Checkout Reference
実装
購入する商品と合計金額が表示されたカート画面から Stripe 決済をおこなうことを想定した、「shop」アプリケーションを作ってみます。
/opt/webapps/myproject/
├── config
│ ├── __init__.py
│ ├── local_settings.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
├── shop
│ ├── apps.py
│ ├── __init__.py
│ ├── urls.py
│ └── views.py
└── templates
├── base.html
├── error.html
└── shop
├── base_shop.html
├── cart.html
└── complete.html
動作確認をおこなった Python および Django のバージョンは以下の通りです。
ソースコードは GitHub に置きました。
github.com
Settings
conf/settings.py (抜粋)
INSTALLED_APPS = [
...
'shop',
]
...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
...
},
},
]
...
PROJECT_APP_PATH = os.path.dirname(os.path.abspath(__file__))
PROJECT_APP = os.path.basename(PROJECT_APP_PATH)
f = os.path.join(PROJECT_APP_PATH, 'local_settings.py')
if os.path.exists(f):
import sys
import imp
module_name = '%s.local_settings' % PROJECT_APP
module = imp.new_module(module_name)
module.__file__ = f
sys.modules[module_name] = module
exec (open(f, 'rb').read())
conf/local_settings.py (抜粋)
DEBUG = True
STRIPE_API_KEY = '<stripe-api-key>'
STRIPE_PUBLISHABLE_KEY = '<stripe-publishable-key>'
上記 2つのキーには、管理画面(ダッシュボード)の[Your account]>[Account settings]>[API Keys]で確認できる、「Test Secret Key」と「Test Publishable Key」をそれぞれ設定します。
https://dashboard.stripe.com/account/apikeys
URLConfs
shop/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^cart$', views.ShowCartView.as_view(), name='cart'),
url(r'^checkout$', views.CheckoutView.as_view(), name='checkout'),
]
Views
shop/views.py
import logging
from django.conf import settings
from django.contrib import messages
from django.shortcuts import render
from django.views.generic import View
import stripe
logger = logging.getLogger(__name__)
class ShowCartView(View):
def get(self, request, *args, **kwargs):
return render(request, 'shop/cart.html', {
'data_key': settings.STRIPE_PUBLISHABLE_KEY,
'data_amount': 500,
'data_name': 'akiyoko blog',
'data_description': 'TEST',
})
class CheckoutView(View):
def post(self, request, *args, **kwargs):
stripe.api_key = settings.STRIPE_API_KEY
token = request.POST['stripeToken']
try:
charge = stripe.Charge.create(
amount=500,
currency='usd',
source=token,
description='This is a test.',
)
except stripe.error.CardError as e:
return render(request, 'error.html', {
'message': "Your payment cannot be completed. The card has been declined.",
})
logger.info("Charge[{}] created successfully.".format(charge.id))
messages.info(request, "Your payment has been completed successfully.")
return render(request, 'shop/complete.html', {
'charge': charge,
})
バックエンドの実装は、Card Payments Quickstart のサンプルのほぼコピペです。
なお、Stripe API を利用するための Python パッケージが必要となるので、pip でインストールしておきます。
$ pip install stripe
Templates
shop/cart.html
{% extends "./base_shop.html" %}
{% block title %}Cart{% endblock title %}
{% block content %}
{{ block.super }}
<form action="{% url 'shop:checkout' %}" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ data_key }}"
data-amount="{{ data_amount }}"
data-name="{{ data_name }}"
data-description="{{ data_description }}"
data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
data-locale="auto">
</script>
{% csrf_token %}
</form>
{% endblock content %}
POST送信時に「csrfmiddlewaretoken」というパラメータを付与しないと 403エラーになるのは、Django に詳しい皆さんには釈迦に説法ですよね。
shop/complete.html
{% extends "./base_shop.html" %}
{% block title %}Complete{% endblock title %}
{% block content %}
<span style="font-size: 0.7rem;">{{ charge }}</span>
{% endblock content %}
動作確認
最後に、実際に検証環境で画面を動かしながら、動作を確認してみます。
ブラウザで「http://localhost:8000/shop/cart」にアクセスし、Stripe のチェックアウトボタン(今回は公式サンプルと同じく「Pay with Card」のまま)をクリックします。
モーダルウィンドウが起動して、クレジットカードの入力フォームが表示されるので、テスト用のクレジットカード情報(*9)を入力して、支払いボタンをクリックします。(なお、テスト用クレジットカード番号を入力した場合は、カードの有効期限、CVC は適当で構いません。)
自サーバ側で Stripe API の即時決済が実行され、shop/complete.html に charge オブジェクトの中身がダンプされました。
Django ベースの ECパッケージとの連携
これまで、クレジットカードの入力フォームを用意していない場合に Stripe の標準フォーム(checkout.js あるいは stripe.js)を利用する方法について解説してきました。
Django Oscar(*10)や Cartridge(*11)などの Django ベースの ECパッケージを使ってクレジットカード情報の入力フォームを利用している場合は、さらに簡単に Stripe API を使うことができます。
例えば、このような感じで(Stripe の標準フォームを使わなくても)サーバ側の処理のみで決済を完結させることができます。
cartridge/shop/payment/stripe_api.py
def process(request, order_form, order):
"""
Payment handler for the stripe API.
"""
data = {
"amount": int((order.total * 100).to_integral()),
"currency": getattr(settings, "STRIPE_CURRENCY", "usd"),
"card": {
'number': request.POST["card_number"].strip(),
'exp_month': request.POST["card_expiry_month"].strip(),
'exp_year': request.POST["card_expiry_year"][2:].strip(),
'cvc': request.POST["card_ccv"].strip(),
'address_line1': request.POST['billing_detail_street'],
'address_city': request.POST['billing_detail_city'],
'address_state': request.POST['billing_detail_state'],
'address_zip': request.POST['billing_detail_postcode'],
'country': request.POST['billing_detail_country'],
},
}
try:
response = stripe.Charge.create(**data)
except stripe.CardError:
raise CheckoutError(_("Transaction declined"))
except Exception as e:
raise CheckoutError(_("A general error occured: ") + str(e))
return response.id
https://github.com/stephenmcd/cartridge/blob/0.12.0/cartridge/shop/payment/stripe_api.py#L24-L49
ほかにも、dj-stripe というパッケージは、「Two Scoops of Django: Best Practices for Django 1.8」の著者として有名な Daniel Greenfeld もプロジェクトに参加しているため、比較的信頼できるプロダクトになっていると推測されます(私自身は全然使ったことはありませんが)。
まとめ
Stripe を使ってみた印象をひと言で言うと、「超絶シンプル」です。これ以上簡単に使える決済サービスが果たしてあるのか?と思ってしまうくらいシンプルです。
PayPal や GMOペイメントゲートウェイなど他の決済代行サービスと比較して、面倒な実装が削ぎ落とされていて最低限の実装だけで決済 API を利用することができたり、管理画面(ダッシュボード)が直感的に使えたりするなど、「開発者に優しい決済サービス」になっている思います。
うぉぉぉぉ!! これが噂の Stripe ってやつか!
熱いぜ! 熱いぜぇぇぇぇぇ!
Stripe、今後日本ですごく流行る予感がします。