Firebase Functions + Stripeでサイトにサブスク機能を導入してみる

コード Node.js

*私の勘違や仕様変更などでこの記事の内容が正しいとは限らないので、こちらの記事の内容を元に実装される際には公式のドキュメントも同時にご確認ください。
この記事・掲載しているコードは2021年12月23日時点での仕様を元に書いています。

要件

下の環境でStripeを試してみます。

フロントエンド:Nuxt.js(JavaScript)
認証:Firebase Auth
バックエンド:Firebase functions
DB:Firebase Firestore
決済:Stripe
決済フォーム:Stripe Payment Element

実装する機能
・認証
・クレカの登録
・サブスクの契約
*コンテンツを提供する部分のコードは後日記事に追加します。

Stripeにプランを登録

Stripeに予めサブスクのプランを登録しておきます。

Stripeのダッシュボードから「商品」→「商品を追加」をクリックします。

あとは、名前や金額など設定する画面が出てくるので適当に入力して保存します。

後々使うので、商品の詳細にある「料金」という項目からprice_から始まるAPI IDを取得できるのでコピーしてどこかにメモしておきます。

functionsの実装

firebase cliでプロジェクトを作成してStripeのライブラリをインストールします。

npm install --save stripe

stripeの導入

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const stripe = require("stripe")("stripeの秘密鍵");
admin.initializeApp();

認証→firestoreをチェック→新規ユーザーだったらstripeにcustomerを作成→firestoreにidを保存する関数

exports.checkuser = functions.https.onCall(async (data, context) => {
  if (context.auth.uid) {
    const c = await admin.firestore().collection("users").doc(context.auth.uid).get();
    if (c.exists) {
      return {status: true};
    } else {
      const customer = await stripe.customers.create({email: context.auth.token.email,});
      const res = await admin.firestore().collection("users").doc(context.auth.uid).set({stripe_customer: customer.id,});
      return {status: true};
    }
  } else {
    return {status: false};
  }
});

*ユーザーの追加をトリガーにfunctionsの関数を叩くみたいな機能もあった気がするので、今後実装するならそっちを使ったほうが良いと思います。

Stripeではフォームを生成するためには一旦バックエンドでclient_secretを作成してフロントエンドのライブラリに渡す必要があります。

下の関数ではStripeに空の支払い方法を作成してclient_secretを生成してフロントエンドに返しています。

exports.set_customer_card = functions.https.onCall(async (data, context) => {
  if (context.auth.uid) {
    const c = await admin.firestore().collection("users").doc(context.auth.uid).get();
    if (c.exists) {
      const setupIntent = await stripe.setupIntents.create({
        customer: c.data().stripe_customer,
        payment_method_types: ["card"],
      });
      return {
        status: true,
        client_secret: setupIntent.client_secret,
      };
    } else {
      return {status: false};
    }
  } else {
    return {status: false};
  }
});

カード番号はブラウザからstripeに直接送られるので、バックエンドでstripeから返ってきたidをcustomerのデフォルトに設定する関数を用意します。

exports.set_customer_default_card = functions.https.onCall(async (data, context) => {
    if (context.auth.uid) {
      const c = await admin.firestore().collection("users").doc(context.auth.uid).get();
      if (c.exists) {
        let new_id = data.stripe_new_id;
        let edit_customer = await stripe.customers.update(c.data().stripe_customer,
          {
            invoice_settings: {
              default_payment_method: new_id,
            },
          }
        );
        return { status: true };
      } else {
        return {
          status: false,
        };
      }
    } else {
      return {
        status: false,
      };
    }
  }
);

サブスクの作成

exports.create_subscription = functions.https.onCall(async (data, context) => {
  if (context.auth.uid) {
    const c = await admin.firestore().collection("users").doc(context.auth.uid).get();
    const subscription = await stripe.subscriptions.create({
        customer:c.data().stripe_customer,
        items:[
          {price:"Stripeで登録したプランのid"}
        ],
      })
      const w = await admin.firestore().collection("users").doc(context.auth.uid).update({
        stripe_subscription:subscription.id
      })
      return {status:true}
  } else {
    return {status: false};
  }
});

サブスクのステータスを確認する処理やキャンセル・クレカの編集などまだまだ不足している部分はありますが、あくまでお試しで書いているアプリなので飛ばします。

フロントエンドの実装

*Stripeやfunctionsに関連しないコードは省略しています。

Nuxtのプロジェクトを生成後FirebaseとStripeのライブラリを導入します。

Stripeはcdn(https://js.stripe.com/v3/)から読み込みました。

バックエンドの関数を読み出すページではfirebase functionsのライブラリを追加でインポートします。

import { getFunctions, httpsCallable } from "firebase/functions";
const functions = getFunctions(firebase)

authで認証した後にcustomerを作成するために記事の一番はじめに書いた関数を呼び出します。

const check_user = httpsCallable(functions,"checkuser")
check_user().then(ans=>{
    if(ans.data.status){
        //別ページへ飛ばすなどcustomer生成後の処理
    }else{
       //バックエンドでcustomerの生成に失敗した場合の処理
    }
})

バックエンドからclient_secretを取得→stripeから返ってきたコードをバックエンドに送信

const stripe = Stripe("公開鍵");
const set_customer_card = httpsCallable(functions, "set_customer_card");
    set_customer_card().then((ans) => {
          if (ans.data.status) {
            const options = {
              clientSecret: ans.data.client_secret,
            };
            const elements = stripe.elements(options);
            // Create and mount the Payment Element
            const paymentElement = elements.create("payment");
            paymentElement.mount("#payment-element");
            const form = document.getElementById("payment-form");
            form.addEventListener("submit", async (event) => {
              event.preventDefault();
              stripe.confirmSetup({
                elements,
                confirmParams: {
                  return_url: "http://localhost:3000/card/success/",
                },
                redirect: "if_required",
              }).then(stripe_ans => {
                  const set_customer_default_card = httpsCallable(functions,"set_customer_default_card")
                set_customer_default_card({stripe_new_id:stripe_ans.setupIntent.payment_method}).then(ans=>{
                      //成功時
                  })
              })
            });
          }else{
              //失敗時
          }
        });

サブスクの契約

const create_subscription = httpsCallable(functions,"create_subscription")
      create_subscription().then(ans=>{
        if (ans.data.status){
          //成功時
        }else{
      //失敗時
        }
      })

参考サイト

Stripe API reference – curl
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.
ドキュメント
Stripe を導入するためのガイドや例をご覧ください。
Cloud Functions for Firebase  |  Firebase Documentation

コメント

タイトルとURLをコピーしました