最近個人的に、Stripeでサブスクリプションを実装する機会があったので、メモがてら流れを書いていこうと思います。
- Laravel 7.0
- Nuxt.js 2.14.6
Nuxt.jsがSPAで動いており、LaravelがAPIを返す構成になっています。
流れ
契約
- stripeのダッシュボード上でプランを作成しておく
- [クライアント] プランを選択してサーバーサイドへ送信
- [サーバーサイド] 顧客情報を作成して、DBへ保存。セッションを作成してクライアントに渡す
- [クライアント] セッションidをstripe checkout(stripeの決済用外部ページ)に渡し、支払い情報を入力させる。成功すれば
/checkout/success?session_id=XXX
などのURLにリダイレクトする - [クライアント] successページのqueryからセッションidを取得。サーバーサイドに渡す
- [サーバーサイド] セッションidから顧客情報を取得、DBへ必要な情報を登録する。
キャンセル
- [クライアント]キャンセル情報をサーバーサイドへ送信
- [サーバーサイド]キャンセル処理(期末で解約とする)
- 期末で解約。stripeからwebhookでサーバーサイドのAPIを叩くことができる。DBに解約の情報を登録。有料機能の制限などを行う
ざっとこんな感じの流れです。
stripe checkoutとelements
まず、使用を迷ったのが、この2つです。
https://stripe.com/docs/billing/subscriptions/checkout
https://stripe.com/docs/billing/subscriptions/fixed-price
これらは、決済の入力フォームを実装するコンポーネントになります。
npmはこちらを使用しました。
checkoutもelementsもこれ1つで利用できます。
https://github.com/jofftiquez/vue-stripe-checkout
checkoutとelementsの違いは、checkoutが外部のサイトで決済処理が完結するのに対して、
elementsの方がカスタマイズ性が高く、サーバーサイドの処理を必要とします。
checkoutでは、外部サイトからリダイレクトで自サイトに戻ることになるので、ユーザーIDなどを渡すのは難しそうに思っていましたが、session_idから簡単に取得できました。必要があれば、metadataとして定義することもできます。
基本的には、checkoutを選択することになるのかなと思いました。
キャンセルの実装について
即キャンセルではなく、支払済の期末での解約にしました。
ちなみに、オプションを'cancel_at_period_end' => true,
とすると、期末のキャンセルにできます。
\Stripe\Subscription::update(
$subscriptionId,
[
'cancel_at_period_end' => true,
]
);
(ちなみに即キャンセルの場合は、updateではなく、\Stripe\Subscription::cancel
となります。)
解約時のwebhook
期末になり、実際の解約となるタイミングで、
stripe上でcustomer.subscription.deleted
というイベントが走ります。
webhookを設定すると指定のイベントでAPIを叩くことができます。
URLは一律になるので、それをtypeでケースを分けて、処理をする形になります。
stripe cliを使うことで、localでもデバッグすることができます。
https://stripe.com/docs/stripe-cli/webhooks
stripe listen --forward-to localhost:5000/hooks
これで、stripe上でイベントが流れるたびに、localhost:5000/hooks
が叩かれることになります。
stripe trigger payment_intent.created
などcli上でイベントを発生させてもいいですし、(イベント一覧)
実際にダッシュボード上や開発中のシステムでstripeのAPIを叩いた場合でも、有効です。
期末解約の実装確認では、キャンセル予定日を確認したのち、stripeのダッシュボード上で即時キャンセルして確認していました。
その他、支払い完了などのタイミングでメールの送信をシステムから行いたい場合などでも使えそうです。
余談: vue-stripe-checkoutのelementsで郵便番号の入力を非表示にする
https://github.com/jofftiquez/vue-stripe-checkout/issues/10
オプションがstylesしか渡せず、困ったのでイシューを探したらありました。
<template>
<div>
<stripe-elements
ref="elementsRef"
:publishableKey="publishableKey"
:amount="amount"
@token="tokenCreated"
@loading="loading = $event"
locale="ja"
>
</stripe-elements>
<v-btn>送信</v-btn>
</div>
</template>
import { StripeElements } from 'vue-stripe-checkout'
export default {
data() {
return {
loading: false,
publishableKey: process.env.STRIPE_PUBLIC_KEY,
amount: 1000,
token: null,
stripeConfig: {
hidePostalCode: true
}
}
},
mounted() {
// configをupdateしてhidePostalCodeする
setTimeout(
() => this.$refs.elementsRef.card.update(this.stripeConfig),
1000
)
},
}