💰

Python(Django)でStripeによる決済システムを導入する①

2021/12/03に公開

株式会社 var でエンジニアインターンをしているひらはらです。

本記事では、決済サービスであるStipeをサイト内へ導入したので紹介します。

背景

弊社では RareTECH という IT スクールを運営しており、その中で受講生の学習が円滑に進むように過去授業の閲覧や講義日程、受講生の一覧情報等を確認できるユーザー管理サイト(ポータルサイト)を提供しています。

これまで入会する方には Line から PayPal の決済リンクへ飛んでもらい決済をして頂いていましたが、Line への登録・決済・ユーザー管理サイトへの登録等、フローが複雑になりつつありました。そこで、ユーザー管理サイトで一括でサイトへの登録とRareTECHでの決済を完結させるために、ユーザー管理サイトへ決済システムの導入を決めました。

また、どの決済サービスを導入するかについては、弊社で開発している Envader の決済で既に Stripe を使用している経緯もあり、Stripe 決済へ移行する運びとなりました。

Stripe とは

Stripe は決済プラットフォームの一つです。ユーザーと事業者間の決済を仲介してくれます。決済手数料は 3.6%と低額で、Stripe を導入するためのドキュメントも豊富ということでエンジニアから高い評価があります。

https://stripe.com/jp

実装

弊社が運営するITスクールである RareTECH では、入会時のみの入会金と定期支払いとして月額もしくは年額のサブスクリプションの 2 つの決済を用意しています。そのため入会金ではCheckout、サブスクリプションではBillingという Stripe のサービスを使用しました。

https://stripe.com/docs/payments/checkout
https://stripe.com/docs/billing

バックエンドではStripeAPIをpythonで使用し、フロントエンドではStripeのJavaScriptライブラリであるStripe.jsを使用しています。

https://stripe.com/docs/api?lang=python
https://stripe.com/docs/js

技術

ユーザー管理サイトでは Django で開発しており、Django を使用しました。また、フロントでは jQuery を使用しています。

  • Django
  • jQuery
  • MySQL

一度きり決済の作成

細かいコードは省きましたが、stripe checkout では概ね次のような実装です。

ユーザーが入会金を支払うボタンをクリックした際、create_checkout_sessionが呼ばれ、決済のセッションを作成し sessionid をフロントに json で返します。フロントでは sessionid を受け取り、Stripe が提供する決済ページへリダイレクトさせています。

ポイントは次のとおりです。

  • modepayment に設定
  • line_items の中の price で商品の ID を指定
  • ユーザーのメールアドレス入力を省略するために ポータルサイトで登録しているcustomer_email を指定
  • success_url に決済が成功したときの URL と CHECKOUT_SESSION_ID を指定
  • フロントに jsonsessionid を返却
@require_POST
@csrf_exempt
def create_checkout_session(request):
    user = request.user
    price_id = json.loads(request.body)
    email = request.user.get_email()

    try:
        checkout_session = stripe.checkout.Session.create(
            payment_method_types=['card'],
            line_items=[
                {
                    'price': price_id,
                    'quantity': 1,
                },
            ],
            mode='payment',
            customer_email=email,
            allow_promotion_codes=True,
            success_url=DOMAIN + 'subscription?session_id={'
                                  'CHECKOUT_SESSION_ID}',
            cancel_url=DOMAIN + 'checkout'
        )

        return JsonResponse({'id': checkout_session.id})
btn.on("click", () => {
  checkout();
});

const checkout = () => {
  const STRIPE_PUBLIC_KEY = $("#stripe-public-key").val();
  const stripe = Stripe(STRIPE_PUBLIC_KEY);
  const value = $("input.admission").val();

  fetch("/create_checkout_session/", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json; charset=UTF-8",
      "X-CSRFToken": "{{ csrf_token }}",
    },
    body: JSON.stringify(value),
  }).then((res) =>
    res
      .json()
      .then((session) => {
        stripe.redirectToCheckout({ sessionId: session.id });
      })
      .catch((err) => console.log(err))
  );
};

一度きり決済成功時の DB への保存

先程の入会金決済が成功するとsuccess_urlで指定したページに遷移します。その際、URLのクエリーとしてCHECKOUT_SESSION_IDも返るため、python の request.GETsessionidとして取得します。その後、sessionid からセッションオブジェクトを取得し、必要なデータを DB へ保存しました。

session_id = request.GET.get('session_id')
session = stripe.checkout.Session.retrieve(session_id)

customer_id = session['customer']
admission_id = session['payment_intent']

サブスクリプションの作成

サブスクリプションでも先程と同様ですが、modesubscriptionを指定しています。また、stripe checkout の入会金決済時に DB へ customer_id を保存したため、こちらでは customer_id を指定しています。

@require_POST
@csrf_exempt
def create_subscription_session(request):
    user = request.user

    price_id = json.loads(request.body)
    customer_id = request.user.get_customer_id()

    try:
        checkout_session = stripe.checkout.Session.create(
            payment_method_types=['card'],
            line_items=[
                {
                    'price': price_id,
                    'quantity': 1,
                },
            ],
            mode='subscription',
            allow_promotion_codes=True,
            client_reference_id=customer_id,
            customer=customer_id,
            success_url=DOMAIN + 'slack?session_id={'
                                  'CHECKOUT_SESSION_ID}',
            cancel_url=DOMAIN + 'subscription'
        )
        return JsonResponse({'id': checkout_session.id})
btn.on("click", () => {
  const value = $('input.input-radio[type="radio"]:checked').val();
  subscription(stripe, value);
});

const subscription = (stripe, value) => {
  fetch("/create_subscription_session/", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json; charset=UTF-8",
      "X-CSRFToken": "{{ csrf_token }}",
    },
    body: JSON.stringify(value),
  })
    .then((res) => {
      return res.json();
    })
    .then((session) => {
      stripe.redirectToCheckout({ sessionId: session.id });
    })
    .catch((err) => console.error(err));
};

サブスクリプション成功時の DB への保存

こちらも先ほどと同様に URL からsession_idのみを取得し、必要なデータを取得して DB へ保存しました。

    session = request.GET.get('session_id')
    stripe_session = stripe.checkout.Session.retrieve(session)
    subscription_id = session['subscription']

まとめ

今回は Django での Stripe 導入について記載しました。
実装自体は諸々含めて1週間もかかっていなかったと思います。実際にStripe自体も公式ドキュメントが豊富で、サンプルコードとStripeAPIドキュメントを読み込みながらDjangoに当てはめていく作業でした。

難しかった点を挙げると、JavaScript の理解はありましたがPython と Django をあまり触ったことがなかったため、Sripe よりも Django のコードを書く要領が悪かったなという印象です。

しかし、今回を通してStripe決済を実装する方法は概ね理解できたので良かったです。
作成以降の管理として webhook 関連の処理については別の記事でまとめたいと思います。

更新

Webhookについての記事を下記のリンクで記載しています。
https://zenn.dev/var/articles/9db03d6d644bea

宣伝

弊社では、インフラ学習サイト Envader(エンベーダー)と IT スクール(RareTECH)を運営しています。また、企業研修やシステム開発なども行っていますので興味がある方は HP よりご連絡下さい。

https://envader.plus

https://raretech.site

https://var.co.jp

GitHubで編集を提案

Discussion