Stripeによるサブスクリプション設計

Stripeを用いてサブスク周りを設計したので、メモとして記します

ざっくりとした仕様としては、以下です
・ 事前に登録したカードでサブスク登録できる
・ 事前にカードを登録してない場合にサブスク登録ボタンを押した際、カード登録画面に遷移させカード登録終了直後にサブスクも登録させる
・ カードは複数登録できて、支払いに使用するカードを選択できる
・ 無料トライアルあり
・ 解約は即時（解約しても期間が残っている間は使える仕様ではない）
・ サブスク課金更新に失敗したら即解約

spaでデザインもオリジナルにしたかったので、checkout(https://stripe.com/docs/payments/checkout )ではなくelements(https://stripe.com/docs/payments/elements )とstripe apiを用いて実装しました

```plantuml
@startuml
title プラン選択画面からのフロー
:加入しているサブスク取得api;
if (加入しているサブスクあるか) then (yes)
  :サブスク解約api;
  stop
else (no)
  :登録可能なサブスク取得api;
  :過去に登録していたサブスク取得api;
  note left: 過去にサブスク登録したこと\nがある場合は無料トライアルなし\nのデザインを表示させたいため
  :サブスク登録api;
  if (支払いに使うカードが登録されているか) then (yes)
  else (no, 400 error)
    :カード追加;
    note left: カード追加のみだとまだ支払いに使うカード\nとして登録されていないので、次のフローで\n支払いに使うカードとして設定する
    :支払いに使うカード変更api;
    :サブスク登録api;
  endif
  stop
endif
@enduml
```

```plantuml
participant front
participant api
participant stripe

note across: 加入しているサブスク取得
front->api: GET /subscriptions?status=active,trialing
api->stripe: Get a customer
return customer
api->front: customer.subscriptions

note across: 登録可能なサブスク取得
front->api: GET /plans
api->stripe: Retreive a price\nstripeのpriceはサブスクを意味する
return price(plan)
api-->front: price(plan)

note across: 過去に登録していたサブスク取得
front->api: GET /subscriptions?status=canceled
api->stripe: List subscriptions(status=canceled)
return canceled subscriptions(=過去に登録していたサブスク)
api->front: 過去に登録していたサブスクのids

note across: サブスク登録
front->api: POST /subscriptions with priceId
api->stripe: Retreive a customer
return customer
api->api: customer.invoice_settings.default_payment_method\nを参照してカード登録の有無を確認

opt カード登録あり
  api->stripe: List subscriptions(status=canceled) 
  opt 過去に課金したことがある
    stripe->api: subscriptions
    api->stripe: Create a subscription without free trial
  end
  opt 過去に課金したことない
    stripe->api: empty array
    api->stripe: Create a subscription with free trial
  end
  stripe->api: success
  api->front: success
end

opt カード登録なし
  api->front: 400 error（must register a card）
end

note across: サブスク解約
front->api: PATCH /subscriptions/:id/cancel
api->stripe: Cancel a subscription
return success
api->front: success

note across: カード一覧取得
front->api: GET /payment_methods
api->stripe: List a customer paymentMethods
return paymentMethods
api->stripe: Retrieve a customer
return customer
api->api: customer.invoice_settings.default_payment_method\nを参照して支払いに登録されているカードを取得
api->front: カード一覧＆支払いに登録されているカード

note across: カード追加
front->api: POST /setup_intents
api->stripe: Create a setupIntent
return secret
api->front: secret
front->front: カード情報入力画面表示
front->front: カード情報入力
front->stripe: confirmCardSetup(stripe.js) with secret\nカード追加
return paymentMethodId

note across: 支払いに使うカード変更
front->api: PATCH /payment_methods/:id/default
api->stripe: Update a customer\n(customer.invoice_settings.default_payment_method)
return success
api->front: success

note across: カード削除
front->api: DELETE /payment_methods/:id
api->stripe: Detach a paymentMethod from a customer
return success
api->front: success

note across: カード情報変更
front->api: PATCH /payment_methods/:id
api->stripe: Update a paymentMethod
return success
api->front: success

note across: 課金成功webhook(サブスク登録直後とサブスク更新時)
stripe->api: invoice.paid\n支払い成功
api->api: 対象の企業に対してサブスク登録済みにdb更新

note across: 課金失敗webhook(サブスク登録直後とサブスク更新時)
stripe->stripe: サブスク解約
stripe->api: invoice.payment_failed\n支払い失敗
api->api: 対象の企業に対してサブスク解約済みにdb更新\nメール送信

note across: サブスク解約webhook（サブスク解約直後）
stripe->stripe: サブスク解約
stripe->api: customer.subscription.deleted
api->api: 対象の企業に対してサブスク解約済みにdb更新
```


# DB設計
stripeへのアクセスを減らすために頻繁にアクセスされる情報はdbに保存するようにすべきなのですが、toB向けサービスで登録企業数も少なく頻繁にアクセスされる情報はなかったので、ユーザー（企業）にstripeのcustomerIdを紐づけるようにしたのと、ユーザーがどのサブスクに登録していてそのサブスクが有効かどうかくらいしかdbに保存させないようにしました

# 解説
サブスク課金更新に失敗したら即解約の仕様を実現するために、ダッシュボードの設定>サブスクリプションおよびメール>失敗した支払いの管理から、リトライスケジュールを無しにして、サブスクリプションのステータスがキャンセルになるように設定しました

ただ支払い失敗のwebhookでも権限剥奪の処理を一応入れてます


事前にカードを登録してない場合のサブスク登録のフローはhttps://stripe.com/docs/billing/subscriptions/build-subscriptions?ui=elements のやり方もあるのですが、事前に登録したカードでサブスク登録できるという仕様もあるので、今回のようなカード登録してサブスク登録するという同期的なやり方にしました

# 所感
Stripeは全体の情報量は多いもののユースケースに対する見本コードが少ないので、シークエンス図を考えるのが大変でした

実際に事前に登録したカードを用いてサブスクを登録するというユースケースに対して見本コードがなかったので、自分でstripeのapiやreactのsdkやダッシュボードを動かしてみたりサポートに問い合わせたりしてこのやり方になりました

24時間体制でサポートしてるので、深夜でもすぐに返答が来たのは助かりました