😇

Stripe Billingのカード更新について考える

2022/08/29に公開

対象読者

本文章はStripe Billingのカード更新に関する実装アプローチに関する内容となっています。Step by Stepのガイドではありません。ある程度Stripe Billingの実装を経験したことがある方を想定ています。


カード更新とはサブスクリプション契約している顧客が契約期間中のカードの更新する行為です。洗い替えの話ではないですので、ご注意ください。
単発決済よりサブスクリプションのカード更新は意外と考慮事項が絡み合って方針を決めるためのアプローチを書いてみました。

複数のサブスクリプションの課金管理

同時に複数の課金期間を持つサブスクリプションサービスを提供している場合、それぞれのサブスクリプションに対して別のカードで課金することを許すかを決める必要があります。

課金プラン カード
月次サブスクリプション カードA
年次サブスクリプション カードB

当然ですが、複数枚のカードを管理すると、実装の複雑さが上がりますし、変更時にまとめて変更するのか、カード有効期限切れのような未払対応時のカード更新対応などを考慮する必要があります。


更新手段

カード情報を新規取得するためのフロントの画面ですが、Customer Portalを利用するかどうかによって分かれます。

Customer Portal

Customer PortalはStripeがHostしているエンドユーザー向けの管理画面です。具体的なサンプル画面はここで確認できます。
Customer Portalで実現できること:

  • インボイスの履歴表示
  • 請求先情報変更
  • 支払い方法変更
  • プロモーションコード適用
  • サブスクリプションをキャンセル・一時停止・更新

また、Customer Portalで実行可能なアクションを制限することも可能です。グローバルでの設定はDashboard から設定できます。もっと細かい粒度で制限したい場合、Configurationを作成します。

const stripe = require('stripe')('sk_test_');

const configuration = await stripe.billingPortal.configurations.create({
  features: {
    customer_update: {
      allowed_updates: ['email', 'tax_id'],
      enabled: true,
    },
    invoice_history: {enabled: true},
  },
  business_profile: {
    privacy_policy_url: 'https://example.com/privacy',
    terms_of_service_url: 'https://example.com/terms',
  },
});

Customer Portalを生成する際に該当Configurationを指定すればDashboardの設定を上書きします。

const stripe = require('stripe')('sk_test_');

const session = await stripe.billingPortal.sessions.create({
  customer: 'cus_XXXX',
  return_url: 'https://example.com/account',
  configuration: 'bpc_XXXX'
});

そうすると通常のCustomer Portalでは請求書履歴だけ有効化にして、問い合わせに応じて必要な項目の変更を開放するようなシナリオを組めます。

Customer Portalは非常に便利ですが、既存のビジネスの場合、すでに上記各プロセスのための画面フローを用意している場合が多いですので、支払い方法の変更だけのためにCustomer Portalに遷移させるのは少し違和感を生じます。また、Customer Portalでカードを更新する場合、カードの管理はユーザーに委ねますので、1ユーザーが複数枚のカードを保持できます。1ユーザー必ず1カードのような要件がある場合、利用が難しくなります。

SetupIntents

Customer Portalを利用しない場合、通常のカード保存と同じようにSetupIntents を利用して新しいカード情報を取得します。

Checkout

カードの変更の手段としてCheckoutを利用できます。下記のようにmodeをsetupにすると決済ではなく、カード情報を取得するための画面となります。

curl --location --request POST 'https://api.stripe.com//v1/checkout/sessions' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer sk_test_XXXX' \
--data-urlencode 'cancel_url=https://stripe.com/docs' \
--data-urlencode 'success_url=https://stripe.com/docs' \
--data-urlencode 'customer=cus_XXXX' \
--data-urlencode 'mode=setup' \
--data-urlencode 'payment_method_types[0]=card'

Payment Elements

別のURLに遷移したくない場合、ページに組み込めるPayment Elementsを利用できます。
Checkoutを含めて詳細なステップは公式のドキュメントにあります。

後処理

SetupIntentsはカードを登録するだけですので、次の課金サイクルにこのカードを利用するという後処理が必要です。具体的な流れとしては:

  1. CheckoutまたはPayment Elementsでカード情報を取得します。
  2. 登録成功のイベント(Webhook(checkout.session.completed)、success_urlなど)を検知します。
  3. Customer ObjectまたはSubscription Objectを更新します。更新する箇所は課金カードの管理で説明します。

課金カードの管理

次の課金時にどのカードで課金するのかを決めるには以下4つの項目があって、上から優先順で課金カードを決めます。

  1. subscription.default_payment_method
  2. subscription.default_source
  3. customer.invoice_settings.default_payment_method
  4. customer.default_source

新しくStripeを始めるのであれば、sourceは無視しても大丈夫かと思います。
subscription.default_payment_methodはCheckoutのSubscriptionモードでSubscription を作成する場合、自動的に設定されますが、customer.invoice_settings.default_payment_methodは基本的に手動で更新する必要があります。

この差異はDashboardのSubscriptionの詳細画面で分かります。Subscription作成時、請求方法はデフォルトの支払い方法に請求となっています。(Customer Objectにあるdefault payment methodに依存します)

一方でもしsubscription.default_payment_methodを指定した場合、支払い方法は明示的にカードが表示されます。

また、複数のサブスクリプションの課金管理に記載したような複数のサブスクリプションが存在する場合、Customer Portalは自動的にサブスクリプション毎の更新オプションを提供し、それを更新するとsubscription.default_payment_method を更新します。


アプローチをまとめる

  • Customer Portalは圧倒的に楽ですので一度Customer Portalを検討するのが無難です。
  • Customer Portalを利用しない場合、下記2つのパターンかと思います。

APIでSubscription を作る場合:

カード情報を取得してから、APIでSubscription を作成していますので、customer.invoice_settings.default_payment_methodでまとめて管理できます。

  1. Customer Objectを作成します。
  2. SetupIntentsでカード情報取得
  3. 取得成功後(Webhook・Return URL)、customer.invoice_settings.default_payment_methodに取得したPaymentMethodを設定します。
  4. APIでSubscription を作成する際にdefault_payment_methodは指定しない(null)
  5. 更新時改めてカード情報を取得します。
  6. 取得成功後(Webhook・Return URL)、customer.invoice_settings.default_payment_methodに更新後のPaymentMethodを設定します。
  7. (Optional)古いカードをDetachします。

ただし、APIの場合でもsubscription.default_payment_methodを設定しても問題ありません。むしろ失敗時の更新を考慮するの時に役に立ちます。

CheckoutでSubscription を作る場合:

CheckoutでSubscription を作る場合、自動的にsubscription.default_payment_methodにPaymentMethodを設定しますので、合わせてカード更新時もSubscriptionを更新します。

  1. Customer Objectを作成します。
  2. Checkout Session (Subscription Mode)を作成します。
  3. 更新時改めてCheckout Session(Setup Mode)を作成して、カード情報を取得します。
  4. 取得成功後、直接subscription.default_payment_methodを更新します。
  5. (Optional)古いカードをDetachします。

失敗時の更新を考慮する

ユーザーからカード変更プロセス以外にカード期限切れなどの理由による課金失敗後の更新依頼も考慮する必要があります。Stripeは課金失敗時に自動的にリマインドメールを送付する機能を提供しています。


このボタンのリンク先は各自定義する必要がありますので、通常のカード変更フローに誘導し、新しいカードを登録した後に再度リトライ できます。
もう一つのOptionはhosted_invoice_urlpayment_settings.save_default_payment_method を利用することも考えられます。
payment_settings.save_default_payment_methodon_subscriptionを設定すると、最後に課金成功したカードをsubscription.default_payment_methodに設定する挙動となります。(CheckoutのSubscription モードを利用できません)
何か嬉しいかというと:

  1. hosted_invoice_urlでは請求の詳細を見ながらカード更新できる
  2. 決済のリトライとカード更新は同時にできる
    もともとhosted_invoice_urlにて課金失敗したカードを更新すると、カードは登録されますが、次の課金用のカードとして更新されません。 よって、課金成功したことを検知して、裏でカード更新の処理は別途必要になります。ただ、payment_settings.save_default_payment_methodと一緒に使うと簡単にカード更新できます。もしこのアプローチを利用する場合、subscription.default_payment_method前提で実装します。

Discussion