【Django】Stripeでサブスクリプション決済を行う
1回限りの決済を行いたい場合は、下記。
【Stripe】Djangoにクレジットカード決済機能を実装させる
対象読者は、カスタムユーザーモデルとDjangoのデフォルトの認証機能を実装済みとする。
【Django】デフォルトの認証機能を網羅し、カスタムユーザーモデルとメール認証も実装させる【脱allauth】
Stripeにてサブスクリプション商品を作る
まず、Stripeにてサブスクリプション商品を作る。下記リンク、商品ページへアクセス。
https://dashboard.stripe.com/test/products?active=true
商品を追加ボタンを押す。
名前と金額、請求期間を指定して、商品を追加する。
追加した商品の詳細ページにアクセス。詳細ページから料金をクリック。
price_.....
と書かれてある部分をクリック。これが 後にsettings.pyに入れる。STRIPE_PRICE_ID
になる。
settings.py
先ほどの商品のAPIキーと、StripeのAPIキーを入れる。
STRIPE_API_KEY = ""
STRIPE_PUBLISHABLE_KEY = ""
STRIPE_PRICE_ID = ""
先ほどの商品のSTRIPE_PRICE_ID
を入れる。
STRIPE_API_KEY
と STRIPE_PUBLISHABLE_KEY
は
https://dashboard.stripe.com/test/dashboard からアクセスして表示されている。
公開可能キーはSTRIPE_PUBLISHABLE_KEY
、シークレットキーはSTRIPE_API_KEY
に 入力。
views.py
セッションの作成から、Stripeへの購入確認などの処理を書いていく。
from django.shortcuts import render, redirect
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
from django.conf import settings
from django.urls import reverse_lazy
import stripe
stripe.api_key = settings.STRIPE_API_KEY
class IndexView(LoginRequiredMixin,View):
def get(self, request, *args, **kwargs):
return render(request, "bbs/index.html")
index = IndexView.as_view()
class CheckoutView(LoginRequiredMixin,View):
def post(self, request, *args, **kwargs):
# セッションを作る
checkout_session = stripe.checkout.Session.create(
line_items=[
{
'price': settings.STRIPE_PRICE_ID,
'quantity': 1,
},
],
payment_method_types=['card'],
mode='subscription',
success_url=request.build_absolute_uri(reverse_lazy("bbs:success")) + '?session_id={CHECKOUT_SESSION_ID}',
cancel_url=request.build_absolute_uri(reverse_lazy("bbs:index")),
)
# セッションid
print( checkout_session["id"] )
return redirect(checkout_session.url)
checkout = CheckoutView.as_view()
class SuccessView(LoginRequiredMixin,View):
def get(self, request, *args, **kwargs):
# パラメータにセッションIDがあるかチェック
if "session_id" not in request.GET:
print("セッションIDがありません。")
return redirect("bbs:index")
# そのセッションIDは有効であるかチェック。
try:
checkout_session_id = request.GET['session_id']
checkout_session = stripe.checkout.Session.retrieve(checkout_session_id)
except:
print( "このセッションIDは無効です。")
return redirect("bbs:index")
print(checkout_session)
# statusをチェックする。未払であれば拒否する。(未払いのsession_idを入れられたときの対策)
if checkout_session["payment_status"] != "paid":
print("未払い")
return redirect("bbs:index")
print("支払い済み")
# 有効であれば、セッションIDからカスタマーIDを取得。ユーザーモデルへカスタマーIDを記録する。
request.user.customer = checkout_session["customer"]
request.user.save()
print("有料会員登録しました!")
return redirect("bbs:index")
success = SuccessView.as_view()
# サブスクリプションの操作関係
class PortalView(LoginRequiredMixin,View):
def get(self, request, *args, **kwargs):
if not request.user.customer:
print( "有料会員登録されていません")
return redirect("bbs:index")
# ユーザーモデルに記録しているカスタマーIDを使って、ポータルサイトへリダイレクト
portalSession = stripe.billing_portal.Session.create(
customer = request.user.customer,
return_url = request.build_absolute_uri(reverse_lazy("bbs:index")),
)
return redirect(portalSession.url)
portal = PortalView.as_view()
class PremiumView(View):
def get(self, request, *args, **kwargs):
if not request.user.customer:
print("カスタマーIDがセットされていません。")
return redirect("bbs:index")
# カスタマーIDを元にStripeに問い合わせ
try:
subscriptions = stripe.Subscription.list(customer=request.user.customer)
except:
print("このカスタマーIDは無効です。")
request.user.customer = ""
request.user.save()
return redirect("bbs:index")
# ステータスがアクティブであるかチェック。
for subscription in subscriptions.auto_paging_iter():
if subscription.status == "active":
print("サブスクリプションは有効です。")
return render(request, "bbs/premium.html")
else:
print("サブスクリプションが無効です。")
return redirect("bbs:index")
premium = PremiumView.as_view()
それぞれurls.pyに登録しておく。
- IndexView: CheckoutViewへPOSTするボタン、サブスク契約済みの場合はPortalViewへGETするリンクを配置
- CheckoutView: セッションを作り、Stripeの決済ページへリダイレクトする
- SuccessView: 決済を確認し、カスタマーIDをユーザーモデルに登録
- PortalView: セッションを作り、Stripeのポータルページへリダイレクトする。
- PremiumView: サブスクリプションが有効かチェックしてページを表示する。
ポータルページとは、契約済みのサブスクリプションを確認・解約するためのページのこと。
urls.py
from django.urls import path
from . import views
app_name = "bbs"
urlpatterns = [
path("", views.index, name="index"),
path("checkout/", views.checkout, name="checkout"),
path("success/", views.success, name="success"),
path("portal/", views.portal, name="portal"),
path("premium/", views.premium, name="premium"),
]
templates/bbs/index.html
カスタマーIDの有無をチェックして、表示させるボタンを変更する。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Hello World test!!</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>
<body>
<form action="{% url 'logout' %}" method="post">
{% csrf_token %}
<input type="submit" value="ログアウト">
</form>
<a href="{% url 'password_change' %}">パスワードの変更</a>
<hr>
{# すでに有料会員登録している場合は、プランにアクセスできるようにする。有料会員ではない場合は、そのリンクを表示させる。#}
{% if request.user.customer %}
<div>
<a class="button" href="{% url 'bbs:portal' %}">有料会員登録の設定をする</a>
<a href="{% url 'bbs:premium' %}">有料会員のサービスを使う</a>
</div>
{% else %}
<form action="{% url 'bbs:checkout' %}" method="post">
{% csrf_token %}
<input type="submit" value="有料会員登録する">
</form>
{% endif %}
</body>
</html>
動かすとこうなる。
有料会員登録のボタンをクリックすると、Djangoのビューでセッションが作られ、Stripeの決済ページへリダイレクト(下記画像)される。
ここでクレジットカード番号を入力し、決済完了をすると、ユーザーモデルのcustomerにカスタマーIDが記録される。
カスタマーIDがある場合は、ポータルサイトへのアクセス(下記画像)ができるようになる。
ここでサブスクリプションの解除が可能。
結論
サブスクリプション処理は、前もって商品を作っておく必要が有る。
処理内容も1回限りの決済処理とは違う点に注意。
Djangoでセッションを作ってStripeへ誘導する点はいずれも同じ。
ソースコード
Django-allauthの仕様変更に伴い、カスタムユーザーモデルとDjangoのデフォルトの認証機能を実装したものに変更した。
https://github.com/seiya0723/django-auth-stripe-subscription
デフォルトの認証機能の実装は下記にて。
【Django】デフォルトの認証機能を網羅し、カスタムユーザーモデルとメール認証も実装させる【脱allauth】