サブスクリプションシステムの構築では、運用が始まってから発覚する運用フローやオペレーションが少なくありません。
例えば、「クレジットカードの有効期限が切れたにも関わらず、カード情報を更新していない」ケース。このケースでは、有効期限が切れた後に行われたサブスクリプションの請求が失敗します。
そのため、システムの要件として次のような機能が必要となります。
- 有効期限切れが迫っているクレジットカードを利用している場合、ユーザーにカード更新を促す通知を送信する
- 年間契約などの長期契約では、更新月が迫っていることと、カードの有効期限を確認することをユーザーに通知する
- サブスクリプションの請求が失敗したことを、ユーザーと社内の顧客サポートチームに通知する
- 発行された請求書の決済が失敗している間、有料版機能の利用を制限する
- アプリケーションUIで、カードの更新が必要であることを強調表示する
- 支払い情報が更新された際、請求に失敗しているサブスクリプション請求書への再決済を行う
- 一定期間を過ぎてもカード情報が更新されない場合、利用停止や解約処理を行う
また、支払いが失敗した場合に備えて、「予備のクレジットカードを登録する」仕組みを作るのも有効です。
例えばCloudflareでは、ユーザーに予備のカード情報を登録するように案内やメール通知を行っています。
[Tips] Stripe Billingで、失敗した請求にノーコードで対応する
Stripe Billingを利用している場合、ダッシュボードから再請求や顧客への通知・決済情報の更新リンク送付を設定できます。
https://dashboard.stripe.com/settings/billing/automatic
https://dashboard.stripe.com/settings/billing/automatic
また、上位プランである「Biiling Scale」を利用すると、再請求のサイクルを機械学習ベースで最適化したり、「カード情報の更新ページ」をStripeが用意するもので案内することができます。
「時間経過による状態変化」は、テストが困難
サブスクリプションでは、「カードの有効期限切れ」以外にも様々な時間経過によるイベントが発生します。
- 契約期間中の解約(即時サービス利用を停止するか、期間終了後まで待つか)
- 契約期間中のプラン変更(差額の請求タイミングやダウングレード時の相殺方法)
- トライアル期間の終了(利用停止のみか、アカウント削除までするか)
このような時間経過を伴うイベント・状態変化をテストするため、Stripeではテストクロック機能を提供しています。
テストクロックで、サブスクリプションの時間経過をテストする
Stripeのテストクロックでは、「Stripeの顧客データの時間を、任意の年月日に変更する」ことができます。
変更したい時間を指定すると、その時間にたどり着くまでに発生するサブスクリプションの請求やスケジュールされたプラン変更などをシミュレーションし、Stripe Webhookのイベントも発火します。
これによって、「有効期限が切れたカードを使い続けると、サブスクリプション契約はどうなるか?」や「契約期間途中のプラン変更で、次の請求がいくらになるか?」といった時間経過が関連する出来事のシミュレーションが簡単に行えるようになります。
テストクロックを、アプリケーションに組み込む方法
テストクロックを利用できるようにするには、「APIが作成するCustomerデータをテストクロックに紐付け」する必要があります。
そこで開発時のポイントを簡単に紹介します。
設計時のポイント
テストクロックをアプリケーションに組み込む場合、「1つのテストクロックに設定できるCustomer数に限りがあること」に注意が必要です。複数人でテストクロックを共有した場合、テストクロックの制限によって、申し込みフローがエラーになる恐れがあります。
また、本番環境ではテストクロックは利用できないため、「テストクロックのIDが指定されていない場合は、テストクロックを利用しない」仕様にすることをお勧めします。
テストクロックを作成する
テストに使用するテストクロックオブジェクトを作成しましょう。
ダッシュボードでの作り方は、こちらの記事をご覧ください。
APIやCLIコマンドから作成することもできます。
環境変数で、テストクロックIDを設定する
テストクロックを作成後、発行されるIDを取得しましょう。APIやCLIの戻り値か、ダッシュボードのテストクロック詳細画面からIDは取得できます。
取得したテストクロックIDを、STRIPE_TESTCLOCK_ID
などの名前で、環境変数に設定しましょう。
STRIPE_TESTCLOCK_ID=clock_xxx
環境変数を利用して、テストクロックを適用するかを判断する
テストクロックIDを取得する関数を作成します。関数にすることで、「本番環境では環境変数を無視してnull
を返す」などの条件分岐の追加・変更が容易になります。
export const getStripeTestclockId = () => {
// StripeのAPIキーに'live'が含まれている場合は、本番環境と判定する
if (process.env.STRIPE_SECRET_API_KEY?.includes('live')) {
return null;
}
return process.env.STRIPE_TESTCLOCK_ID;
}
この例では、StripeのAPIキーにlive
が含まれている場合を持って、本番環境と判定しています。
これ以外にも、NODE_ENV
やLaravelのapp()->isProduction()
など、言語・フレームワークから本番環境を判定することも可能です。
テストクロックを顧客・サブスクリプションに設定する
取得したテストクロックIDを利用して、作成する顧客・サブスクリプションデータをテストクロックに紐付けましょう。
テストクロックはStripeのCustomerデータに紐付けします。そのため、Customer作成時のAPIリクエストに、テストクロックのIDを追加しましょう。
const testClockId = getStripeTestclockId()
const customer = await stripe.customers.create({
name: 'xxx',
test_clock: testClockId || undefined
})
test_clock
がundefined
の場合は、Customerデータはどのテストクロックにも割り当てされません。そこでテストクロック取得関数getStripeTestclockId
の戻り値がnull
やundefined
の場合は、undefined
を渡すように実装しています。
あとは作成したCustomerデータを利用して、サブスクリプションやCheckout Sessionを作成すればOKです。
サブスクリプション申し込みを受け付けるCheckout Sessionにテストクロックを追加する場合のコード差分を、次に紹介します。
+ const testClockId = getStripeTestclockId()
+ const customer = await stripe.customers.create({
+ test_clock: testClockId || undefined
+ })
const session = await stripe.checkout.sessions.create({
+ customer: customer.id,
+ customer_update: {
+ name: 'auto',
+ },
mode: 'subscription',
line_items: [{
price: 'price_xxxx',
quantity: 1
}],
success_url: 'http://example.com',
})
return c.json(session)
Stripe Webhookで、顧客・サブスクリプションデータの削除に対応する
作成したテストクロックは、作成から30日が経過すると自動的に削除されます。
そのため、Stripe側で削除されたテストデータがアプリケーション側のDBに残らないように連携処理を作る必要があります。
-
customer.subscription.deleted
: そのサブスクリプションが削除・解約された時に呼び出されるWebhookイベント -
customer.deleted
: そのCustomerが削除された時に呼び出されるWebhookイベント
この2つのWebhookイベントを処理するAPIを用意し、必要に応じてアプリケーション側のユーザーデータやDB情報を削除・更新するようにしましょう。
Webhook APIは、Stripeのドキュメントを利用して雛形を簡単に作ることができます。
https://stripe.com/docs/webhooks/quickstart
署名シークレットの検証方法なども、雛形に含まれていますので、ぜひご活用ください。
まとめ
Stripeでサブスクリプション決済を提供している場合、テストクロックを利用して様々な時間経過イベントをシミュレーションできます。
ただしアプリケーションに組み込む際には、StripeのCustomerオブジェクトを作成する際に、テストクロックのIDを設定する必要があります。また、「1つのテストクロックに設定できるCustomerデータなどに制限がある」ことや「複数人で時間を変更すると、意図しない時間経過が発生する恐れがある」ため、開発者やテストケースごとにテストクロックを作成することをお勧めします。
テストクロックを利用したテスト方法の詳細は、ドキュメントの「テストクロック ユースケース」などをご覧ください。
また、テストの支払い方法を活用することで、様々な決済シナリオを試すことが可能です。
テストクロックを活用して、サブスクリプションシステムの開発をより効率化していきましょう!
Stripe テストクロックの関連記事