見出し画像

Djangoでサブスクリプション商品の決済を行う(stripe)

最近は、サブスクリプションが主流になってきましたよね。

Adobeが買い切りモデルからサブスクリプションモデルに変更して復活した話は有名ですし、Amazon PrimeやNetfilxによってサブスクリプションに加入した人も多いのではないでしょうか?

こうしたサブスクリプションを導入して、お客さんを獲得するとことができるといいことがあります。

それは、毎月の売り上げが0から始まらないということです。つまり、何もしなくても一定の売り上げが見込めるわけです。登録者と毎月の解約率(Churn rate)チャーンレート)がわかっていれば、予測の立てやすい売り上げになります。

お客さんにとっても毎月安定して同じようなサービスを安定して得ることができるので、毎月どこから購入しようか悩むことがないというメリットがあります。

そんな、サブスクリプション商品をDjangoで作る方法について紹介します。

 DjangoとStripeのインストール

スクリーンショット 2021-06-03 9.12.29

今回もStripeの決済システムを利用していきます。決済システムのような重要度が高い部分に関しては、すでに実績があって安定しているシステムを使う方がずっと安心です。

Stripeでサブスクリプションを作るには2つの部品を作成する必要があります。

1つは、商品を作成してその支払いにお客さんが登録できる部分

2つ目は、そのサブスクリプションを管理する部分です。

2つ目は、例えばお客さんが解約したくなったり、支払いが滞った場合のことを考えると必須ですよね。

まずは今回使う仮想環境を作成して、仮想環境に入っておきます。

python3 -m venv stripe_subscription_env
source stripe_subscription_env/bin/activate

そして、必要なパッケージをインストールします。とりあえず、DjangoとStripeだけで大丈夫です。

pip install django
pip install stripe

Django側の準備

Djangoでプロジェクトを作成して、Stripeを使えるようにしていきましょう。

stripe_subscription_testというプロジェクト名で、subscriptionというアプリケーションを作成します。

django-admin startproject stripe_subscription_test
cd stripe_subscription_test
python manage.py startapp subscription

settings.pyのINSTALLED_APPSにsubscriptionを追加し、

# settings.py

INSTALLED_APPS = [
  ...省略...

  'subscription.apps.SubscriptionConfig'
]

ルーティングを設定しておきます。

# stripe_subscription_test/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
  path('admin/', admin.site.urls),
  path('', include('subscription.urls')),
]
# subscription/urls.py

from django.urls import path
from . import views

app_name = 'subscription'
urlpatterns = [
 path('', views.SubscriptionIndexView.as_view(), name='index'),
]

Viewはテンプレートを表示するだけの簡単なテンプレートです。

from django.views import generic

class SubscriptionIndexView(generic.TemplateView):
  template_name = "subscription/index.html"

このテンプレートも仮のテンプレートを用意しておきます。subscriptionアプリケーション内に、templatesとsubscriptionフォルダを作成し、index.htmlを作成しておきます。ファイル構成は以下のようになっています。

stripe_subscription_test
|- payment
 |- templates
   |- payment
     |- index.html
# index.html
this is index page

ここまでの設定がうまくいっているか確認するために、一度開発用サーバーを立ち上げてみます。

python manage.py runserver
↓
http://localhost:8000 にブラウザでアクセス

以下の文字列が表示されれば成功です。

スクリーンショット 2021-06-03 9.23.00

これでDjango側の準備は完了です。

stripe側の設定

Stripeの初期設定に関しては、以下の記事の同じ項目を参照してください。

この記事では、上記の設定が完了していることを前提に進めます。

公開可能キーとシークレットキーが入手できたら次に進みます。

Stripeで商品を作成する

まずは、Stripeでサブスクリプションで提供したい商品を登録する必要があります。Stripe CLI(Command Line Interface)を使う方法もありますが、今回はダッシュボードから作成します。

ダッシュボードにログインして、左のメニューの「商品」をクリックします。

スクリーンショット 2021-06-03 9.29.43

「テスト商品を追加」から商品を追加しましょう。

スクリーンショット 2021-06-03 9.30.11

テスト商品なので深く考えずに適当に商品を作成します。3つ商品を作成して、よくある3つの選択肢を用意することにします。

スクリーンショット 2021-06-03 9.33.50

ところで、料金体系に4つ選択肢がありますね(標準の料金体系、パッケージ料金体系、段階的な料金体系、数量ベースの料金体系)。

標準の料金体系:1点いくら
パッケージ料金体系:何点か組み合わせていくら(12本セット販売とか)
段階的な料金体系:数量によって料金が変わる(最初の10個はいくら)
数量ベースの料金体系:合計数によって料金が変わる(10個ならX円/個)

普通は標準の料金体系になるかと思います。

3名分など組み合わせてパッケージとして買ってもらいたいならパッケージ料金体系。

大きい会社用にたくさん買って貰えば安くなるようにしたいなら段階的か数量ベースを使うことになるでしょう。

今回は、標準の料金体系にしておきます。

スクリーンショット 2021-06-03 9.44.05

情報を設定したら、右上の「商品を保存」で保存します。

同じことをあと2回繰り返して商品を3つ作っておきます。

スクリーンショット 2021-06-03 9.46.13

この商品を開くと、料金欄にAPI IDがあるのでそれを控えておきます。

スクリーンショット 2021-06-03 9.46.57

stripeのサブスクリプション支払い処理を実装する

必要なViewとそのテンプレート、ルーティングを用意します。実装するのは以下になります。

success.html:サブスクリプション登録が完了した時に遷移するページ
cancel.html:登録がキャンセルされた時に遷移するページ
index.html:サブスクリプション商品を表示するページ
create_checkout_session:stripeとセッションを繋げる関数

まずはルーティングを実装します。

# subscription/urls.py

from django.urls import path
from . import views
app_name = 'subscription'
urlpatterns = [
  path('', views.SubscriptionIndexView.as_view(), name='index'),
  path('create_checkout_session/', views.create_checkout_session, name='checkout_session'),
  path('success/', views.SubscriptionSuccessView.as_view(), name='success'),
  path('cancel/', views.SubscriptionCancelView.as_view(), name='cancel'),
]

上記に対応するViewも作成します。シンプルにテンプレートを表示するだけのViewです。create_checkout_sessionは後で実装します。

# subscription/views.py

from django.views import generic

class SubscriptionIndexView(generic.TemplateView):
  template_name = "subscription/index.html"

class SubscriptionSuccessView(generic.TemplateView):
   template_name = "subscription/success.html"
   
class SubscriptionCancelView(generic.TemplateView):
   template_name = "subscription/cancel.html"
   
def create_checkout_session(request):
   pass

簡単なsuccess.htmlとcancel.htmlを先に作成してしまいましょう。どんなページか書いて、商品ページに戻るだけのシンプルなページです。

# success.html

<html>
<head>
 <title>購入ありがとうございます!</title>
</head>
<body>
 <section>
   <h1>購入ありがとうございます!</h1>
   <a href="{% url 'subscription:index' %}">商品ページにもどる</a>
 </section>
</body>
</html>​
# cancel.html

<html>
<head>
 <title>購入がキャンセルされました</title>
</head>
<body>
 <section>
   <h1>購入がキャンセルされました</h1>
   <a href="{% url 'subscription:index' %}">商品ページにもどる</a>
 </section>
</body>
</html>​

ここでindex.htmlを実装しましょう。index.htmlで実施することは、商品の表示と、登録ボタンを押したら購入画面に遷移することです。

# index.html

<head>
   <title>商品一覧</title>
   <script src="https://js.stripe.com/v3/"></script>
</head>
<body>

   <section>
       <div class="product">
           <h3>Python学習コースBasic</h3>
       </div>
       <button type="button" class="checkout-button" value="price_1Iy4fiEHMnMqYb9vOb35dhkb">Basicコースに登録する</button>
   </section>

   <section>
       <div class="product">
           <h3>Python学習コースAdvanced</h3>
       </div>
       <button type="button" class="checkout-button" value="price_1Iy4glEHMnMqYb9vEYPHcbNs">Advancedコースに登録する</button>
   </section>

   <section>
       <div class="product">
           <h3>Python学習コースPremium</h3>
       </div>
       <button type="button" class="checkout-button" value="price_1Iy4hDEHMnMqYb9vTngHPUGH">Premiumコースに登録する</button>
   </section>


   <script type="text/javascript">
       // Create an instance of the Stripe object with your publishable API key
       var stripe = Stripe("pk_test_あなたの公開可能キーを入れてください");
       var checkoutButtons = document.querySelectorAll(".checkout-button");
    
       checkoutButtons.forEach(function(checkoutButton){
           checkoutButton.addEventListener("click", function () {
           fetch("/create_checkout_session/", {
               method: "POST",
               headers: {
                   'Accept': 'application/json',
                   'Content-Type': 'application/json; charset=UTF-8',
                   'X-CSRFToken': '{{ csrf_token }}'
                   },
               body: JSON.stringify({
                   priceId: checkoutButton.value
                   })
               })
               .then(function (response) {
               return response.json();
               })
               .then(function (session) {
               return stripe.redirectToCheckout({ sessionId: session.id });
               })
               .then(function (result) {
               // If redirectToCheckout fails due to a browser or network
               // error, you should display the localized error message to your
               // customer using error.message.
               if (result.error) {
                   alert(result.error.message);
               }
               })
               .catch(function (error) {
               console.error("Error:", error);
               });
           });
       });
     </script>
</body>​

基本的には、通常の購入とそこまで変わりません。異なるのは、違うボタンをクリックしたら、各ボタンのvalueに設定してあるpriceIdをstripeに渡すようにしていることです。

querySelectorAllでcheckout-buttonのボタンをすべて取得し、forEachで各ボタンに、ボタンを押した時の処理を追加しています。データをPOSTする際は、fetch内のbodyに値を設定して渡しています。

var stripe = Stripe("pk_test_あなたの公開可能キーを入れてください")は、あなたの公開可能キーをに変更してください。もちろんvalueのところもあなたの商品のpriceIdに変更してください。

さて、ここでボタンをクリックすると、create_checkout_sessionに遷移します。この関数を実装してstripeにpriceIdを渡すようにしましょう。

# subscription/view.py

import stripe
import json
from django.urls import reverse
from django.http import JsonResponse

...省略...

def create_checkout_session(request):
   stripe.api_key = 'sk_test_あなたのシークレットキーを設定してください'

   post_data = json.loads(request.body.decode("utf-8"))

   try:
       checkout_session = stripe.checkout.Session.create(
           payment_method_types=['card'],
           line_items=[
               {
                   'price': post_data['priceId'],
                   'quantity': 1,
               },
           ],
           mode='subscription',
           success_url=request.build_absolute_uri(reverse('subscription:success')),
           cancel_url=request.build_absolute_uri(reverse('subscription:cancel')),
       )
       return JsonResponse({'id': checkout_session.id})
   except Exception as e:
       return JsonResponse({'error':str(e)})

単純な購入と異なるのは、mode='payment'がmode='subscription'になっていることと、line_itemに商品情報を入れるのでなく、先ほどボタンから渡したpriceIdを設定していることです。前回の記事と比較してみてください。

POSTしたデータはjson.loads(request.body.decode("utf-8"))で取得しています。このようにすることで、post_data['priceId']という形でPOSTしたデータにアクセスすることができます。

stripe.api_key = 'sk_test_あなたのシークレットキーを設定してください'の部分は、あなたのシークレットキーに変更してください。

実装完了

これでひとまずサブスクリプション登録の実装が完了したので、開発用サーバーを実行して試してみましょう。

python manage.py runserver
↓
http://localhost:8000/ にブラウザでアクセス

ブラウザでアクセスすると3つの商品が表示されます。

スクリーンショット 2021-06-03 11.39.14

ボタンをクリックするとstripeの決済ページに遷移します。

スクリーンショット 2021-06-03 11.39.39

支払いが成功するテスト用のクレジットカード情報を利用して、完了するかテストしてみます。

スクリーンショット 2021-06-03 11.40.40

処理が終わると、購入ページに遷移しました。

スクリーンショット 2021-06-03 11.41.35

上にある左矢印をクリックすれば、

スクリーンショット 2021-06-03 11.42.13

キャンセルページに遷移します。

スクリーンショット 2021-06-03 11.42.36

購入が完了したので、商品ページでも定期支払いが1が有効ですになっていますね!

スクリーンショット 2021-06-03 11.50.27

まとめ

これでひとまず、サブスクリプションによる定期支払いが可能になりました。ただ、支払いが失敗した時の対応をどうするかとか、お客さんに支払い情報を更新してもらう設定とか、実はまだまだ色々することがあります( ̄▽ ̄;)

でも記事が長くなって来たので、今回はここまで!笑

ここまで読んでいただけたなら、”スキ”ボタンを押していただけると励みになります!(*´ー`*)ワクワク

この記事が気に入ったらサポートをしてみませんか?