僅か数行で超簡単にstripeでのサブスク実装ができるWeb SDKがヤバい！@stripe/firestore-stripe-paymentsを使ってみよう！

こんにちは。[virapture株式会社](https://virapture.com)の[もぐめっと](https://mogmet.com)です。
![mogmet.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/9d0afab4-977f-06dd-89ce-053fa1d70235.jpeg)
先日シーサーになりました。シーサーのごとし、怒涛の顔でプロダクトを世に出指定こおうと思いました。

ところで、先日のFirebase Summit 2021でも発表されたFirestore Stripe Paymentsのweb sdkがリリースされたので早速使ってみました。

https://github.com/stripe/stripe-firebase-extensions/blob/web-sdk/firestore-stripe-web-sdk/README.md

APIのリファレンスとしては一覧になってます。

https://github.com/stripe/stripe-firebase-extensions/blob/next/firestore-stripe-web-sdk/markdown/firestore-stripe-payments.md

これを使ってサンプルを書いてみたので、実際の使い方の説明とともにサンプルを説明いたします。

前提としてstripeとfirebaseの設定は終わっているものとします。
終わってない方は下記を参考に設定しておいてください。

https://qiita.com/mogmet/items/cddc182156028c928ecf

## サンプルのゴール

完成品はこちらになります。

https://github.com/mogmet/nuxt-stripe-web-sdk-sample/blob/main/pages/index.vue

今回のサンプルのゴールはClient SDKを使って、商品の表示と購入ができ、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/48477f56-38ee-a2f4-4ecb-0886fe575625.png)

購入した後は購入したサブスクの情報を表示するまでをゴールとします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/8f3f8d00-69c8-5905-3f06-ffe70497c553.png)

~~できればpure jsでやりたかったのですが、いい感じにnpmにあがっているjsを組み込む方法がわからなかったのでnuxtにしてしまいました。~~

全体のサンプルコードを掲載しておきます。

```vue
<template>
  <v-row>
    <!-- 課金済みは現在課金している情報を表示 -->
    <v-col v-if='this.subscriptions.length > 0'>
      <v-card v-for='subscript in subscriptions' :key='subscript.id'>
        <v-card-title>{{getProductTitle(subscript.product)}}</v-card-title>
        <v-card-text>{{subscript.current_period_start}} - {{subscript.current_period_end}}</v-card-text>
      </v-card>
    </v-col>
    <!-- 商品一覧を表示 -->
    <v-col v-for="product in products" v-else :key="product.id">
      <v-img :src='product.images[0]' height='240' aspect-ratio='3' />
      <v-card :loading='isLoading'>
        <v-card-title>{{product.name}}</v-card-title>
        <v-card-subtitle>{{product.description}}</v-card-subtitle>
        <v-card-text>
          <p>{{product.prices[0].unit_amount}}{{product.prices[0].currency}} / {{product.prices[0].interval_count}}{{product.prices[0].interval}}</p>
        </v-card-text>
        <v-card-actions><v-btn :loading='isLoading' @click='onClickPurchase(product.prices[0].id)'>購入</v-btn></v-card-actions>
      </v-card>
    </v-col>
  </v-row>
</template>

<script lang='ts'>
import {
  createCheckoutSession,
  getProducts,
  onCurrentUserSubscriptionUpdate,
  // eslint-disable-next-line import/named
  Product,
  // eslint-disable-next-line import/named
  Subscription
} from '@stripe/firestore-stripe-payments'
import Vue from 'vue'
import { signInAnonymously } from 'firebase/auth'
import { payments } from '~/stripe'
import { auth } from '~/firebase/firebase'
interface PageStripeData {
  products: Product[],
  subscriptions: Subscription[],
  isLoading: boolean
}
export default Vue.extend({
  name: 'Stripe',
  data(): PageStripeData {
    return {
      products: [] as Product[],
      subscriptions: [] as Subscription[],
      isLoading: false
    }
  },
  async created() {
    await signInAnonymously(auth)
    // 商品一覧を取得
    const products = await getProducts(payments, {
      includePrices: true,
      activeOnly: true,
    })
    this.products = products
    // 既に課金済みであればサブスク情報をセットする
    onCurrentUserSubscriptionUpdate(payments, snapshot => {
      this.subscriptions = snapshot.changes.map(value => value.subscription)
    })
  },
  methods: {
    /// サブスク購入処理
    async onClickPurchase(priceId: string): Promise<void> {
      this.isLoading = true
      try {
        const session = await createCheckoutSession(payments, {
          price: priceId,
          success_url: window.location.origin,
          cancel_url: window.location.origin
        })
        window.location.assign(session.url)
      } catch (error) {
        alert(error)
      } finally {
        this.isLoading = false
      }
    },
    /// 商品名を返す
    getProductTitle(productId: string): string {
      return this.products.find(product => product.id === productId)?.name ?? ''
    }
  }
})
</script>
```

## ソースコード解説

web sdkを使っている箇所としては、初期化、商品の取得、サブスク購入処理、サブスク情報取得の4つになります。

### 初期化

```github.com/mogmet/nuxt-stripe-web-sdk-sample/blob/main/stripe.ts
import { getStripePayments } from '@stripe/firestore-stripe-payments'
import { app } from '~/firebase/firebase'

export const payments = getStripePayments(app, {
  productsCollection: "products",
  customersCollection: "customers",
});
```

getStripePaymentsメソッドにfirebaseを渡してあげることで初期化されます。
productsCollectionとcustomersCollectionはExtensionで設定した時のfirestoreのcollection pathを入れておきます。

以降はことあるごとにこのpaymentsを一緒に渡すことでstripeの処理を行いますので、どこからでも呼び出せるようにしておくといいと思います。

### 商品の取得

ココからはすべて下記ファイルの説明になります。

https://github.com/mogmet/nuxt-stripe-web-sdk-sample/blob/main/pages/index.vue

```ts
    const products = await getProducts(payments, {
      includePrices: true,
      activeOnly: true,
    })
```

たったこれだけで今有効化状態の商品一覧を取得できます。
とってもシンプルにかけますね。

他にも[whereで条件を絞ることもできる](https://github.com/stripe/stripe-firebase-extensions/tree/web-sdk/firestore-stripe-web-sdk#list-products-and-prices)みたいです。

```js
const products = await getProducts(payments, {
  includePrices: true,
  activeOnly: true,
  where: [
    ["metadata.type", "==", "books"],
    ["metadata.rating", ">=", 4],
  ],
  limit: 10,
});
```

### サブスク購入処理

```ts
        const session = await createCheckoutSession(payments, {
          price: priceId, // productに紐付いているpriceIdを指定する
          success_url: window.location.origin, // 購入成功先のURL
          cancel_url: window.location.origin // 購入キャンセル先のURL
        })
        window.location.assign(session.url)
```

createCheckoutSessionでセッション情報を取得してstripeのページに遷移します。
たったこれだけです！！！
pure jsだとfirestoreに書き込みされるのを待ったりとかしないといけないのでココが一番web sdkの恩恵をうけますね。

### サブスク情報取得

```ts
    onCurrentUserSubscriptionUpdate(payments, snapshot => {
      this.subscriptions = snapshot.changes.map(value => value.subscription)
    })
```

onCurrentUserSubscriptionUpdateメソッドを使うことでsubscriptionの購入状況によってリアルタイムに変更を受け取ることができます。
changesの中にはそれぞれsubscriptionとtypeがあって、typeは"added" | "modified" | "removed"の3つの状態を振り分けることができます。
これを見ておけばサブスクを購入しているかどうかがすぐ丸わかりです。
簡単ですね。

## まとめ

getStripePayments, getProducts, createCheckoutSession, onCurrentUserSubscriptionUpdateのメソッドを使うことでより簡単にwebでのサブスク課金が実装できるようになりました！
pure jsのサンプルをみると、結構入り組んだ実装だったので振り返ると大分楽ができていい感じですね。

https://github.com/mogmet/firebase-subscription-payments/blob/master/public/javascript/app.js

年々課金実装が気軽にできるようになってきているのでどんどん課金を実装していきましょう！！

最後に、[ワンナイト人狼オンライン](https://online.1nite-jinro.com/link/blog)というゲームを作ってます！よかったら遊んでね！

https://online.1nite-jinro.com/link/blog

他にも[Camecon](https://camecon.me/)や[Offcha](https://offcha.com/)といったサービスも作ってるのでよかったら使ってね！

https://camecon.me/

https://offcha.com/

また、チームビルディングや技術顧問、Firebaseの設計やアドバイスといったお話も受け付けてますので御用の方は弊社までお問い合わせください。

https://virapture.com/