見出し画像

Stripeで請求書対応を行ったサブスクリプション構築

これはJP_Stripes Advent Calendar 2020 11日目の記事です。

わたしは株式会社EBILABというスタートアップでエンジニアとして働いており、主にアプリケーション側の実装をメインに担当しております。
今年技術面で挑戦したことの一つとしてStripeを使ったサブスクリプション構築に取り組みましたので、今回はそこで学んだことのまとめとして書いていきたいと思います。

今年の5月にあった【オンライン開催】JP_Stripes (Stripe ユーザーグループ)九州・沖縄リージョンでは「Stripe Billingを導入してサブスクリプションを実装した話」というタイトルで登壇させていただきました。

今回の記事では、さらに他決済サービスと連携して請求書支払いに対応した内容を書いていきたいと思います。

今回は以下の技術、フレームワークを用いて実装を行いました。

Nuxt.js v2.14.1
Laravel v6.16.0
GraphQL (Node.js, Apollo)
Stripe API
Azure (Azure WebApp, Logic Apps)
請求書支払いサービスAPI


まずはお客様が新規でこちらのサービスのサブスク購読を行えるまでの実装について説明していきます。

以下は処理の流れについての概略図となっています。

画像1

では手順を一つずつ説明していきます。

今回はクレジット支払いと請求書支払いの2つに対応したので、それぞれで処理が異なるので一つずつ説明していきます。

クレジット支払いの場合

クレジット支払いの場合、当たり前ですが、お客様に使用するクレジットカードの情報を入力してもらわなければなりません。
しかし、こちらで入力フォームを用意するとログに各お客様のクレジットカードの情報が残ってしまい、セキュリティ面の問題に発展していく恐れがあります。
なので、これらを防止するためにStripeではそれらの入力フォームを丸々用意してくれる仕組みがあります。
その一つにStripe Checkoutという仕組みがあります。

これを使うとStripeのクライアントを経由して以下のようなページを呼び出すことができます。

画像2

今回はこちらを利用して、お客様にクレジットカードの情報を入力してもらい、サブスクリプション登録までの処理を実装しました。
実際の処理の流れは以下の通りとなっています。

画像3

↓実際のコード

Nuxt.jsのcheckout作成APIリクエスト部分

const stripe = Stripe(process.env.STRIPE_API_PUBLIC_KEY);
this.$apollo
  .mutate({
    mutation: mutationCreateStripeCheckout,
    variables: {
      plan: plan,
      quantity: this.contractCount,
      email: email,
      coupon: couponCode
    },
  })
  .then(response => {
    stripe.redirectToCheckout({
    sessionId: response.data.createStripeCheckout.id,
   });
  })
  .catch(e => {
    console.error(e);
  })

Nuxt.jsのsubscription登録APIリクエスト部分

mounted() {
   this.$apollo
     .mutate({
       mutation: mutationCreateStripeSubscription,
       variables: {
         sessionId: this.$route.query.session_id,
         planId: planId,
         coupon: coupon
         name: name
       },
     })
 }

Laravelのcheckout作成部分

\Stripe\Stripe::setApiKey(env('STRIPE_API_SECRET_KEY'));
$session = \Stripe\Checkout\Session::create([
  'customer_email' => $request->input('email'),
  'payment_method_types' => ['card'],
  'subscription_data' => [
    'items' => [
      [
        'plan' => $request->input('plan'),
        'quantity' => $request->input('quantity'),
      ],
    ],
    'trial_from_plan' => true,
    'default_tax_rates' => [
      env('STRIPE_TAX_RATE_10')
    ]
  ],
  'success_url' => env('NUXT_DOMAIN') . "/signup/callback?session_id={CHECKOUT_SESSION_ID}&plan_id={$request->input('plan')}&coupon={$request->input('coupon')}",
  'cancel_url' => env('NUXT_DOMAIN') . '/signup/confirm_plan ,
]);

Laravelのsubscription登録部分

$session = \Stripe\Checkout\Session::retrieve($request->input('sessionId'));
$customerId = $session['customer'];
$subscriptionId = $session['subscription'];
$subscription = \Stripe\Subscription::retrieve($subscriptionId);
$entryQuantity = $session['display_items'][0]['quantity'];
$paymentMethodId = $subscription['default_payment_method'];
$customer = \Stripe\Customer::update(
  $customerId,
  [
    'name' => $request->input('name'),
    'invoice_settings' => ['default_payment_method' => $paymentMethodId],
  ]
);
       
$updateData = ['default_payment_method' => $paymentMethodId];
if ($request->input('coupon')) {
  $updateData['coupon'] = $request->input('coupon');
 }
 $stripeSubscription = \Stripe\Subscription::update($subscriptionId, $updateData);

これでクレジット支払いでの新規登録が完了します。
支払いに関してはStripe側で自動で行ってくれるので、処理を任せることができます。

請求書支払いの場合

続いて請求書支払いについて説明していきます。
こちらは使う請求書支払いサービス次第で仕様が変わると思うのですが、今回私が使ったサービスでは、顧客の与信審査があったので、新規で請求書払での登録を行ってもらう際にはそれを考慮した実装を行う必要がありました。

また、その与信審査の結果がいつ終了したかをこちらから確認しなければならず、そのあたりでかなり独自の実装になってしまった気がします。。

それを踏まえて処理の流れとしては

①入力してもらったお客様のユーザー名やアドレスなどの情報を、請求書支払いサービスにAPIを通して登録する
②Stripe上でもcustomer, subscriptionを作成する(stripe上のpaymentMethodはinvoiceを選択)
③こちらのDBにもcustomer, subscription情報を保存。ただしsubscriptionのステータスを「申請中」のステータスにして保存する

一旦これで新規登録が終わります。
新規登録の中での処理だけで言えば、クレジット支払よりもシンプルです。

↓実際のコード

Nuxt.jsの顧客登録のAPIへリクエストする部分

const plan = this.paymentPeriod == 'annual' ? process.env.STRIPE_YEAR_PRICE : process.env.STRIPE_MONTHLY_PRICE;
this.$apollo
    .mutate({
        mutation: mutationCreateNPCustomer,
        variables: {
            name: name,
            addressNumber: addressNumber,
            address: address,
            phoneNumber: phoneNumber,
            email: email,
            plan: plan,
            quantity: quantity
        },
    })
    .then(response => {
        this.$router.push('/signup/complete');
    })
    .catch(e => {
        this.$store.dispatch('toast/setToast', { classType: 'danger', message: 'エラーが発生しました' });
        console.error(e);
    })

Laravelのsubscription登録部分

$stripe = \Stripe\Stripe::setApiKey(env('STRIPE_API_SECRET_KEY'));
$customer = \Stripe\Customer::create([
    'name' => $request->input('name'),
    'email' => $request->input('email'),
    'metadata' => [
        'invoice_payment_service_user_id' => $result['userId']
    ]
]);
$stripeSubscription = \Stripe\Subscription::create([
    'customer' => $customer['id'],
    'items' => [
        [
            'plan' => $request->input('plan'),
            'quantity' => $request->input('quantity'),
        ]
    ],
    'collection_method' => 'send_invoice',
    'trial_period_days' => 14,
    'days_until_due' => 14,
    'default_tax_rates' => [
        env('STRIPE_TAX_RATE_10')
    ],
]);
       

Stripe上ではそれぞれのオブジェクトでmetadataを持つことができるので、外部サービスのデータなどもそこで管理することで紐付けることができます。

そのあと請求書支払いサービス側の審査がおりたかどうかを確認するバッチを実装し、おりていたらこちらのDBの subscriptionを有効にするように実装しています。

(ここではバッチ処理について詳しくは説明しないのですが、Azureが提供しているAzure Logic Appsというワークフローをかんたんに構築することができるサービスを使って実装しました。)

では支払いはどうするのか。となりますが、ここではStripeが用意しているWebhookを使うことで支払い処理を実装しました。


Stripe上では様々なイベントを受け取ることができるので、それぞれのイベントに対応するWebhookのエンドポイントに、こちらの行いたい処理を行うエンドポイントを設定することができます。

ここではStripe側で請求が確定したイベントを表すinvoice.finalizedというイベントのWebhookに、外部請求書支払いサービス側で請求を行うリクエストをするエンドポイントを設定することで、請求書でもStripeの支払いサイクルに合わせて請求を行うことができます。

画像4

終わりに

これでひとまず新規登録では請求書支払いの外部サービスとの連携を行うことができました。

外部サービスの仕様などでかなり独自の実装になってしまったので、もっとうまいやり方がありそうです。。

あと今回のAdvent Calendarでこのような記事を見つけたので、かなり気になっています...!

まだPilot版のようですが、これを使うことで銀行振込(請求書支払い)でもすべて一貫してStripeのレールで実装ができ、管理するデータもStripe上に集約できそうなので、かなり良さそう。

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