FirebaseとStripeでサブスクを実装してみた感想とざっくりと実装の紹介
TL, DR
- 実装自体はかなり楽でした(まだ解約などを実装していないので途中まで実装した人の肌感です)
- Stripeが提供してくれている購入フォームがとても優秀!!!
- 商品の追加はCloud Functions経由でしかできなかった(違ってたらごめんなさい)
- checkout_sessionsサブコレクションを作成してからsubscriptionサブコレクションが作成されるらしい
調べながら実装しているので、間違った点などございましたらコメントなどで指摘して頂けるとありがたいです。
参考にした記事
Firebase ExtensionsのRun Subscription Payments with Stripeを使ってサブスク課金をコードを書かずに実装する
stripe-samples/firebase-subscription-payments
実装方針
商品の追加編
基本的に1つ目の記事が優秀過ぎてそのままなぞっていくだけで出来ました。
だた、今回はユーザが商品の追加を出来るようにする必要があったのでそこがハマり所でした。
少し調べたところこちらのライブラリなどを使うのが一般的のようなのですが
これはサーバサイドでしか使えません。
商品の追加のためだけにExpressでサーバを立てるのも面倒だったのでCloud Functions経由で商品を追加することにしました。
ソースコードをそのまま載せることは出来ないのでStripeの公式ドキュメントに沿って流れだけ説明しますとCloud Functions内で
const stripe = new Stripe(functions.config().stripe.apikey, { apiVersion: '2020-08-27' });
こんな感じで初期化してから、
~~~何か処理する~~~
const product = await stripe.products.create({
name: '商品名',
description: '商品の説明文'
});
await stripe.prices.create({
unit_amount: 100,
currency: 'jpy',
recurring: { interval: 'month' },
product: product.id
});
と書いてあげると、FirebaseにStripeの拡張機能をインストールした時Products and pricing plans collection
で指定したルートコレクションにドキュメントが追加されていると思います。
決済編
さて、苦労したのは決済です。
実際に自分で手を動かしてどのドキュメントが作成されなんのフィールドが追加されるのかを追うだけで一日かかってしまいました(僕の実力の無さによるものです。反省。)
結論から申し上げますと、先ほど紹介したリポジトリの中のapp.jsを見れば終わりです。
流れとしては
- FirebaseにStripeの拡張機能をインストールする時に
Customer details and subscriptions collection
で指定したコレクションのドキュメントのサブコレクションにcheckout_sessions
ドキュメントを作成する - 1が実行されると
Customer details and subscriptions collection
で指定したコレクションのドキュメントにStripeId
とStripeLink
が追加される - エラーがなければStripeの決済フォームに飛ばす
こんな感じです。
文字で説明しても伝わりにくいと思うので少しコードも交えながら説明していきます。
ユーザがボタンを押してhandleClick
関数が実行されたとしましょう。
const stripePromise = loadStripe(process.env.STRIPE_API_KEY);
const handleClick = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault();
const docRef = await firestore()
.collection('customers')
.doc(uid)
.collection('checkout_sessions')
.add({
price: 'price_xxxxxxxxxxxxxxxxxxxxx',
allow_promotion_codes: true,
tax_rates: [process.env.STRIPE_TAX_RATE],
success_url: 'http://localhost:3000/',
cancel_url: 'http://localhost:3000/',
metadata: { tax_rate: '10% sales tax exclusive' },
});
docRef.onSnapshot(async (snap) => {
const { error, sessionId } = snap.data();
error && alert(`An error occured: ${error.message}`);
if (sessionId) {
const stripe = await stripePromise;
stripe.redirectToCheckout({ sessionId });
}
});
};
customers
というのがCustomer details and subscriptions collection
で指定したコレクション名です。
stripe.redirectToCheckout({ sessionId })
部分で実際にStripeの決済フォームに飛びます。
僕がハマったのは
- priceの値は価格ではなくStripeの商品のAPI IDであること
- tax_ratesにはarrayを渡す必要がある
この2点でした。
1は最初リポジトリのサンプルコードを読んだ時に
priceだから価格を入れればいいのかな〜
とか考えて動かず悩みました。
素直にドキュメントのIDを渡してやれば良かったのですね。
2は最初そのまま文字列で渡してエラーを吐かれてまたサンプルリポジトリを確認しに行って
const taxRates = ['txr_1HCshzHYgolSBA35WkPjzOOi'];
と書いてあるのに気がつき、そこでようやく
文字列じゃなくて配列で渡すのか
と気付きました。
みなさんが同じ轍を踏まないことを願います。
最後に
思いつきで記事を書いたのですが、後半がだいぶ雑になってしまったので分からないとこがありましたらコメントを頂けるとお答えできる範囲で対応させて頂きます。
ではでは、良いFirebase Lifeを。
Discussion