FirebaseとStripeでサブスクを実装してみた感想とざっくりと実装の紹介

## TL, DR
- 実装自体はかなり楽でした(まだ解約などを実装していないので途中まで実装した人の肌感です)
- Stripeが提供してくれている購入フォームがとても優秀！！！
- 商品の追加はCloud Functions経由でしかできなかった(間違っていたらごめんなさい)
- checkout_sessionsサブコレクションを作成してからsubscriptionサブコレクションが作成されるらしい

調べながら実装しているので、間違った点などございましたらコメントなどで指摘して頂けるとありがたいです。

## 参考にした記事
[Firebase ExtensionsのRun Subscription Payments with Stripeを使ってサブスク課金をコードを書かずに実装する](https://qiita.com/mogmet/items/cddc182156028c928ecf)

[stripe-samples/firebase-subscription-payments](https://github.com/stripe-samples/firebase-subscription-payments)

## 実装方針

### 商品の追加編

基本的に１つ目の記事が優秀過ぎてそのままなぞっていくだけで出来ました。

だた、今回はユーザが商品の追加を出来るようにする必要があったのでそこがハマり所でした。

少し調べたところこちらのライブラリなどを使うのが一般的のようなのですが

[stripe/stripe-node](https://github.com/stripe/stripe-node)

これはサーバサイドでしか使えません。

商品の追加のためだけにExpressでサーバを立てるのも面倒だったのでCloud Functions経由で商品を追加することにしました。

ソースコードをそのまま載せることは出来ないので[Stripeの公式ドキュメント](https://stripe.com/docs/api/prices/create?lang=node)に沿って流れだけ説明しますとCloud Functions内で

```ts
const stripe = new Stripe(functions.config().stripe.apikey, { apiVersion: '2020-08-27' });
```

こんな感じで初期化してから、

```ts
~~~何か処理する~~~
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](https://github.com/stripe-samples/firebase-subscription-payments/blob/master/public/javascript/app.js)を見れば終わりです。

流れとしては

1. FirebaseにStripeの拡張機能をインストールする時に`Customer details and subscriptions collection`で指定したコレクションのドキュメントのサブコレクションに`checkout_sessions`ドキュメントを作成する
2. 1が実行されると`Customer details and subscriptions collection`で指定したコレクションのドキュメントに`StripeId`と`StripeLink`が追加される
3. エラーがなければStripeの決済フォームに飛ばす

こんな感じです。

文字で説明しても伝わりにくいと思うので少しコードも交えながら説明していきます。

ユーザがボタンを押して`handleClick`関数が実行されたとしましょう。

```ts
  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(error.message);
      if (sessionId) {
        const stripe = await stripePromise;
        stripe.redirectToCheckout({ sessionId });
      }
    });
  };
```

`customers`というのが`Customer details and subscriptions collection`で指定したコレクション名です。

`stripe.redirectToCheckout({ sessionId })`部分で実際にStripeの決済フォームに飛びます。

僕がハマったのは

1. priceの値は価格ではなくStripeの商品のAPI IDであること
2. tax_ratesにはarrayを渡す必要がある

この2点でした。

1は最初リポジトリのサンプルコードを読んだ時に

> priceだから価格を入れればいいのかな〜

とか考えて動かず悩みました。

素直にドキュメントのIDを渡してやれば良かったのですね。

2は最初そのまま文字列で渡してエラーを吐かれてまたサンプルリポジトリを確認しに行って

```ts
const taxRates = ['txr_1HCshzHYgolSBA35WkPjzOOi'];
```

と書いてあるのに気がつき、そこでようやく

> 文字列じゃなくて配列で渡すのか

と気付きました。

みなさんが同じ轍を踏まないことを願います。

## 最後に

思いつきで記事を書いたのですが、後半がだいぶ雑になってしまったので分からないとこがありましたらコメントを頂けるとお答えできる範囲で対応させて頂きます。

ではでは、良いFirebase Lifeを。