初めに
Stripeを使えば比較的簡単に決済機能を実装できます。この記事ではStripeが用意した決済フォームにページをリダイレクトさせることで、自サイトではクレジットカード情報を保持することなく支払い機能を自サイトに組み込むことができます。
基本はStripeのドキュメントとGithub上のサンプルを基にして実装しています。また、Djangoで実装します。正直、試行錯誤しながら適当に作ったので実装に関して突っ込みどころ満載です。
最終的にはStripeのサンドボックスの劣化版が実装されます。また、以下の情報は2020年3月時点のものです。
環境設定
- Python==3.8.1
- Django==3.0.4
- stripe==2.43.0
- django-environ==0.4.5
Step0: 前準備
適当にディレクトリを作成する。
mkdir stripe-test
cd stripe-test
pipenv
を使って仮想環境を構築します。
pipenv --python 3.8
pipenv install django stripe django-environ
pipenv shell
で仮想環境を起動する。pipenvについてさらに詳しく知りたい場合はPipenvを使ったPython開発まとめを参照。
# shell起動
pipenv shell
Django
プロジェクトを作成する。
django-admin startproject stripe_test .
django-admin startapp checkout
Stripeにアカウント登録
Stripeにアカウント登録する。アカウント作成やダッシュボードの見方についてはRailsのdeviseとStripe(とBootstrap)の組み合わせで、ただただユーザー登録させたユーザーに定期更新購読させるだけのappを作りました②に詳しい。
公開ビジネス情報
を入力する。2020年3月現在だと以下のURL。
次に開発用の公開キーとシークレットキーを確認する。2020年3月現在だと以下のURL。
Django Setting
Djangoのsettingを以下のようにする。.env
ファイルを取り込むのにdjango-environ
を使用している。
import os
import environ
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_NAME = os.path.basename(BASE_DIR)
# django-environから環境変数を読み込む
env = environ.Env()
env.read_env(os.path.join(BASE_DIR, '.env'))
### 省略 ###
INSTALLED_APPS = [
...,
'stripe',#追加
'checkout.apps.CheckoutConfig', # 自作 チェックアウト
]
### 省略 ###
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], #デフォルトから変更
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
### 省略 ###
# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
STRIPE_SECRET_KEY = env('STRIPE_SECRET_KEY')
STRIPE_PUBLISHABLE_KEY = env('STRIPE_PUBLISHABLE_KEY')
.env
には例えばこういう設定を行う。使用通貨がアメリカドル(usd)の場合はセント単位で計算するので1234と入力した場合は$12.34となる。
# Stripe API keys
STRIPE_PUBLISHABLE_KEY=pk_12345
STRIPE_SECRET_KEY=sk_12345
# Checkout settings
DOMAIN=http://localhost:8000/
BASE_PRICE=1099
CURRENCY=usd
Step1:サーバー側でチェックアウト・セッションを実装する
セッションの作成はサーバー側で行う。Python + Django でJSONの送受信を参考にして、以下のようにDjangoを使って、Checkout Sessionを作成する。
# -*- coding: utf-8 -*-
import logging
import os
import json
from django.shortcuts import render
from django.conf import settings
from django.views import generic
import stripe
from django.http.response import JsonResponse
from django.views.decorators.csrf import csrf_exempt
logger = logging.getLogger(__name__)
@csrf_exempt
def onetime_payment_checkout(request):
if request.method == 'POST':
data = json.loads(request.body)
domain_url = os.getenv('DOMAIN')
stripe.api_key = settings.STRIPE_SECRET_KEY
try:
# Create new Checkout Session for the order
# ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
checkout_session = stripe.checkout.Session.create(
success_url=domain_url +
"checkout/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url=domain_url + "checkout/canceled/",
payment_method_types=["card"],
line_items=[
{
"name": "Pasha photo",
"images": ["https://picsum.photos/300/300?random=4"],
"quantity": data['quantity'],
"currency": os.getenv('CURRENCY'),
"amount": os.getenv('BASE_PRICE'),
}
]
)
logger.debug( str(checkout_session))
return JsonResponse({'sessionId': checkout_session['id']})
except Exception as e:
logger.warning( str(e) )
return JsonResponse({'error':str(e)})
Step2:チェックアウトにリダイレクト
基本的にJavascriptを使って実装していく。実現していることはstripe.js
を使ってStripe側にデータを渡して、Stripeで決済してもらった後に自サイトに戻る。
{% load i18n static %}
<!DOCTYPE html>{% get_current_language as LANGUAGE_CODE %}
<html lang="{{ LANGUAGE_CODE|default:"en-us" }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<title>{% block title %}{% endblock %}</title>
<script src="{% static 'js/jquery-3.3.1.slim.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
{% block additional_script %}
{% endblock %}
</head>
<body>
<div class="container">
{% block content %}
{% endblock %}
</div>
</body>
</html>
{% extends "base.html" %}
{% load i18n static %}
{% block title %}Checkout_test{% endblock title %}
{% block additional_script %}
<script src="https://js.stripe.com/v3/"></script>
<script src="{% static 'checkout/js/script.js' %}"></script>
{% endblock additional_script%}
{% block content %}
<div>
<h1 data-i18n="headline"></h1>
<h4 data-i18n="subline"></h4>
<div class="pasha-image">
<img
src="https://picsum.photos/280/320?random=4"
width="140"
height="160"
/>
</div>
</div>
<div class="quantity-setter">
<input type="number" id="quantity-input" min="1" max="2" value="1" />
</div>
<p class="sr-legal-text" data-i18n="sr-legal-text"></p>
{% csrf_token %}
<button
id="submit"
>PAYMENT</button>
</section>
<div id="error-message"></div>
</div>
{% endblock content %}
自作Scriptは以下のサイトを参考にした
/* Handle any errors returns from Checkout */
var handleResult = function(result) {
if (result.error) {
var displayError = document.getElementById("error-message");
displayError.textContent = result.error.message;
}
};
// Create a Checkout Session with the selected quantity
var createCheckoutSession = function() {
var inputEl = document.getElementById("quantity-input");
var quantity = parseInt(inputEl.value);
return fetch("/checkout/create-checkout-session/", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
quantity: quantity,
})
}).then(function(result) {
return result.json();
});
};
/* Get your Stripe publishable key to initialize Stripe.js */
fetch("/checkout/config")
.then(function(result) {
return result.json();
})
.then(function(json) {
window.config = json;
var stripe = Stripe(config.publicKey);
// Setup event handler to create a Checkout Session on submit
document.querySelector("#submit").addEventListener("click", function(evt) {
createCheckoutSession().then(function(data) {
stripe
.redirectToCheckout({
sessionId: data.sessionId
})
.then(handleResult);
});
});
});
HTMLとJavascriptが実装できたら、checkout_test.htmlが見られるようにviews.py
を以下を追加する。
class IndexView(generic.TemplateView):
template_name = "checkout/checkout_test.html"
def get(self, request, *args, **kwargs):
return render(request, self.template_name )
とりあえずはcheckout
アプリのマッピング情報を記述して、最初のページだけ見られるようにする。これはcheckout/urls.py
を以下のように編集することで実現できる。
from . import views
from django.urls import path,include
app_name = 'checkout'
urlpatterns = [
path('',views.IndexView.as_view(), name="index"),
]
Djangoアプリではない、ホームページ側のurls.py
(この記事ではstripe_test/stripe_test/urls.py
)を以下のように修正する。
from django.contrib import admin
from django.urls import path,include
from django.views.generic import RedirectView
urlpatterns = [
path('admin/', admin.site.urls),
path('', RedirectView.as_view(url='/checkout')),
path('checkout/', include('checkout.urls')),
]
上記自作のjavascript(stripe_test/static/checkout/js/script.js
)ではfetch("/checkout/config")
を行っているが、これはjsonでstripeAPIの公開キーを受け取っている。サーバー側の実装は以下のとおり。
@csrf_exempt
def stripe_config(request):
if request.method == 'GET':
stripe_config = {
'publicKey': settings.STRIPE_PUBLISHABLE_KEY,
'basePrice': os.getenv('BASE_PRICE'),
'currency': os.getenv('CURRENCY'),
}
return JsonResponse(stripe_config, safe=False)
from . import views
from django.urls import path,include
urlpatterns = [
...,
path('config/',views.stripe_config),
...,
]
python manage.py runserver
でとりあえずサイトを起動してhttp://localhost:8000/
にアクセスすると以下の感じの画面が出るはずだ。
ただ、これだとPAYMENT
を押しても、何も起きないはず。なぜなら、ボタン押されたあとの実装を行っていないから。具体的には自作javascriptのcreateCheckoutSession
関数はとあるURLにPOSTしているが、Django側で設定していないので、設定を行う必要がある。それにはurls.py
にcreate-checkout-session
を追加すればいい。
urlpatterns = [
...,
path('create-checkout-session/',views.onetime_payment_checkout , name='create-checkout'),
...,
]
以上まで実装を行えば、Stripe側にURLが飛んで決済情報を入力する画面が表示されるはずです(参考:Stripe Checkout)。後は、決済情報を入力し終わったリダイレクト先のページ(success.html
とcanceled.html
)を実装していけば、一通り決済はできるようになっている。
#テスト
テスト用の決済カードは複数用意されているので、適宜選ぶ。特にこだわりがないのならば 4242424242424242
を使用。
もし、うまく実装できていればダッシュボード上に支払いが反映されているので、確認してみてください。
参考:Github
上記の実装はGithubに公開しているので、参考にしてください。