akiyoko blog

akiyoko の IT技術系ブログです

Stripe 決済の最新事情 〜 Django と Stripe と私 〜

この投稿は 「Django Advent Calendar 2016 - Qiita」 の 4日目の記事です。



今日は クリスマス Advent Calendar の最終日ですが、4日目の記事を書いています。
というのも、自分が担当した 12/5 の「ベスト・オブ・Django本! - akiyoko blog」の前日だけ何故かずーっと空いていて、気持ち悪かったので。。

f:id:akiyoko:20161224150324p:plain



この記事では、「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 に準拠)なフォームを簡単に作成できる
    • フォームの自動ローカライズにも対応 *5
  • 管理画面(ダッシュボード)が見やすい
  • 三井住友カードが全面バックアップしている(らしい)ので安心 *6
  • 売上金の円建て振り込みが可能(手数料は週一回の振り込みまで無料)
  • 定期支払い(継続課金)も利用可能
  • Stripe Radar, CVC, ZIP code などによる不正使用対策が充実

 

デメリット(マイナス面)
  • 現時点で JCB カードが利用不可 *7, *8
  • 銀行振込み・コンビニ支払いは利用不可(クレジットカードのみ)


現状、大きなデメリットは JCBカードが使えないというほぼ一点のみかと思われます。
それを許容できるのであれば、Stripe は決済サービスの筆頭候補になり得るはずです。


(参考)


 

Stripe 決済の仕組みは?

Stripe が推奨しているオンライン即時決済フローは、下図のようなものになります。
f:id:akiyoko:20161224150344p:plain

① ショッピングカート画面(「Stripe で支払う」ボタン)
    ↓
② 決済モーダルウィンドウ(「支払う」ボタン)
    ↓
③ 決済完了画面


 
すごくシンプルですよね。

Stripe 決済の接続方式は「直接決済方式」で、Stripe サイト等にリダイレクトされることなく決済を完了させることができます。つまり、エンドユーザが「Stripe」を意識することがないので、ユーザの離脱防止に効果が期待できます。




次に、Stripe がユーザのクレジット情報を処理するまでの詳細な仕組みについて、順を追って説明します。

Step 1:Securely collecting payment information

(参考)Card Payments Quickstart

  1. フォームに Stripe 謹製の「checkout.js」を設置しておく。
  2. ユーザがボタンをクリックすると、クレジットカード(およびその他の)情報を入力するためのモーダルウィンドウが立ち上がる。
  3. モーダルウィンドウ内でクレジットカード情報を入力して決済ボタンをクリックすると、checkout.js が裏側で Stripe の API とやり取りをおこない、数分間だけ有効になるトークンを生成してから、自サーバのアクションに POSTリクエストを実行する(トークンを「stripeToken」というパラメータで送信)。

 

Step 2:Creating Charges

(参考)Card Payments Quickstart

  1. 自サーバ側でトークンを受け取り、Stripe の API を利用して決済処理を実行する(トークンも渡す)


たったこれだけ。

フォームに埋め込むスクリプトもほんの数行だけですし、サーバ側の処理もリクエストからトークンを受け取って Stripe API を使って即時決済を実行するだけです。


ユーザが入力したクレジットカード情報は、Stripe API によって自動的にトークンとして自前に暗号化され、自サーバ側には流れてこないので安全 です。ただし、Stripe API とのやり取りをセキュアに保つためには、フォームを置くサイトを SSL化しておく必要があります。



なお、Step 1 でトークンを生成する手段は、

  • Checkout.js(簡単標準フォーム)
  • Stripe.js(カスタマイズ可能なフォーム)
  • Mobile SDKs

の 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 のバージョンは以下の通りです。

  • Python 2.7.6
  • Django 1.10



ソースコードは GitHub に置きました。
github.com



 

Settings

conf/settings.py (抜粋)

# Application definition

INSTALLED_APPS = [
    ...
    'shop',
]
...
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        ...
        },
    },
]
...
# LOCAL SETTINGS
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 KEY #
##############
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
f:id:akiyoko:20161224194651p:plain



 

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,  # Amount in cents
            'data_name': 'akiyoko blog',
            'data_description': 'TEST',
        })


class CheckoutView(View):
    def post(self, request, *args, **kwargs):
        # Set your secret key: remember to change this to your live secret key in production
        # See your keys here: https://dashboard.stripe.com/account/apikeys
        stripe.api_key = settings.STRIPE_API_KEY

        # Get the credit card details submitted by the form
        token = request.POST['stripeToken']

        # Create a charge: this will charge the user's card
        try:
            charge = stripe.Charge.create(
                amount=500,  # Amount in cents
                currency='usd',
                source=token,
                description='This is a test.',
            )
        except stripe.error.CardError as e:
            # The card has been declined
            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」のまま)をクリックします。
f:id:akiyoko:20161224210831p:plain

モーダルウィンドウが起動して、クレジットカードの入力フォームが表示されるので、テスト用のクレジットカード情報(*9)を入力して、支払いボタンをクリックします。(なお、テスト用クレジットカード番号を入力した場合は、カードの有効期限、CVC は適当で構いません。)
f:id:akiyoko:20161224210853p:plain

自サーバ側で Stripe API の即時決済が実行され、shop/complete.html に charge オブジェクトの中身がダンプされました。
f:id:akiyoko:20161224210908p:plain



 

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 ってやつか!
熱いぜ! 熱いぜぇぇぇぇぇ!

f:id:akiyoko:20161221075723p:plain:w350



Stripe、今後日本ですごく流行る予感がします。



オススメ Django 本

最後に、Django のベストプラクティス本の紹介です。

Two Scoops of Django: Best Practices for Django 1.8

Two Scoops of Django: Best Practices for Django 1.8


記事を書きましたので、是非ご参考に。


<過去記事>
akiyoko.hatenablog.jp

*1:決済サービスの接続方式については、過去記事「Django と Paypal と私(主に PayPal 決済の最新事情について) - akiyoko blog」を参考のこと

*2:Stripe: Press resources より

*3:2017年4月末でのサービス終了が宣言された「WebPay」では、海外向けでの販売のみを前提とした利用はできないと規定されていました。 https://webpay.jp/faq#constraints

*4:Stripe: Pricing を参照

*5:「data-locale」パラメータを「auto」に設定することで実現可能。パラメータの詳細については Checkout Reference を参照

*6:Stripeが日本で正式ローンチ、三井住友カードが資本参加を発表 | TechCrunch Japan より

*7:Which cards and payment types can I accept with Stripe? : Stripe: Help & Support より

*8:【徹底比較】話題の5大オンライン決済サービスPayPal、SPIKE、Stripe、WebPay、Yahoo!ウォレット FastPay、を比べてみた(比較表有り) には「順次対応予定」とは書いてありましたが。。

*9:https://stripe.com/docs/testing#cards にテストで使用できるクレジットカード情報がリストアップされています

*10:過去記事「ゼロからはじめる Django で ECサイト構築(その3:Django Oscar の機能を調べる) - akiyoko blog」を参照

*11:Cartridge は、Mezzanine 専用に作られた、Mezzanine に ECサイト機能を搭載するためのアプリケーションです。Mezzanine 公式ページには「Ecommerce / Shopping cart module」と紹介されています。