前回の続き。
インストールしてサンプルが動かせたけど、
どうなってるかわからないので、もうすこしドキュメント見てみる。
とりあえず、FirebaseコンソールのExtensionsにある
「この拡張機能の動作」を見ていく。
前回までで「Configuring the extension」を見ながら進めたので、
「Using the extension」の続きを確認する。
まとめ
Firestoreの構造はこんな感じ。(コレクション名はデフォルト)
- customers/{uid} ... 顧客 - checkout_sessions/{id} ... 顧客のチェックアウトセッション - subscriptions/{id} ... 顧客のサブスク - payments/{id} ... 顧客の一回限り - products/{id} ... 商品 - prices/{id} ... 価格 - products/tax_rates - tax_rates/{id} ... 税率
大体の流れ。
1. 決済の流れ
- 商品の情報は
products/{id}
やprices/{id}
を取得 - 決済準備のときに、
checkout_sessions/{id}
を作成 - 準備ができたら、Stripe.jsでURLを生成
- 諸々の設定は
checkout_sessions/{id}
作成時にする
2. 購入状態の確認
- サブスクは
subscriptions/{id}
をみる - 一回限りは
payments/{id}
をみる - メタデータを使えば、Firabase Authのカスタムクレームでも可
3. 解約などはカスタマーポータル
- URLは用意されたFunctionsで生成
「Using the extension」で書かれていること
もろもろの使い方が書いてる感じ。
このあたりを読めば、使い方がわかる。
Sign-up users with Firebase Authentication
Firebase Authenticationを使ったStripeの顧客を作成する方法について。
FirebaseUIライブラリを使うと簡単だよ、とのこと。
拡張機能設定の
「Sync new users to Stripe customers and Cloud Firestore」について書かれてる。
拡張機能設定でSyncにするとAuthのユーザ作成時にStripeの顧客を作成する。
Do not syncにすると最初にStripeのチェックアウトセッション作成時にStripeの顧客を作成する。
List available products and prices
有効な商品や価格の取得方法。
Firestoreでの取得方法が書かれているけど、こんな感じ。
products/{productsId}
に商品products/{productsId}/prices/{priceId}
に価格- 商品には
active
フィールドがあり、有効かどうかのフラグを持つ。
const query = db.collection('products').where('active', '==', true); const querySnapshot = async query.get(); querySnapshot.forEach(async function (doc) { console.log(doc.id, ' => ', doc.data()); const priceSnap = await doc.ref.collection('prices').get(); priceSnap.docs.forEach((doc) => { console.log(doc.id, ' => ', doc.data()); }); });
Start a subscription with Stripe Checkout
Stripe Checkoutで決済を開始する方法。
流れとしては、こんな感じ
- 価格IDを含む、
customers/{uid}/checkout_sessions
のDocを作成 - Fuctionsでセッションを作成し、ドキュメントにsessionIdを保存
- ドキュメントが更新されたら、sessionIdとStripe.jsを使って、決済ページに移動
Stripe Checkout自体については、Stripeの公式ドキュメントを見ると良い感じ。
・Checkout とカスタマーポータルを使用した定期支払い
const docRef = await db .collection('customers') .doc(currentUser.uid) .collection('checkout_sessions') .add({ price: 'price_1GqIC8HYgolSBA35zoTTN2Zl', success_url: window.location.origin, cancel_url: window.location.origin, }); // Wait for the CheckoutSession to get attached by the extension docRef.onSnapshot((snap) => { const { error, sessionId } = snap.data(); if (error) { // Show an error to your customer and // inspect your Cloud Function logs in the Firebase console. alert(`An error occured: ${error.message}`); } if (sessionId) { // We have a session, let's redirect to Checkout // Init Stripe const stripe = Stripe('pk_test_1234'); stripe.redirectToCheckout({ sessionId }); } });
Importing Stripe.js as ES module
Stripe.jsをES moduleとしてimportする方法。
上の「Start a subscription with Stripe Checkout」の例だと、
こんな感じに書き換えることができる。
import { loadStripe } from '@stripe/stripe-js'; docRef.onSnapshot(async (snap) => { const { error, sessionId } = snap.data(); if (sessionId) { const stripe = await loadStripe('pk_test_1234'); stripe.redirectToCheckout({ sessionId }); } });
Handling trials
トライアルについての扱い方。
デフォルトは価格のオプションで設定した「無料トライアル」が適用される。
ただ、再登録などでトライアルを適用したくないユーザ等の場合は、
checkout_sessions
を作成するときにtrial_from_plan: false
を指定すると、
トライアルが適用されなくなる。
const docRef = await db .collection('customers') .doc(currentUser.uid) .collection('checkout_sessions') .add({ price: 'price_1GqIC8HYgolSBA35zoTTN2Zl', trial_from_plan: false, success_url: window.location.origin, cancel_url: window.location.origin, });
Applying discount, coupon, promotion codes
Stripeでは割引クーポンにも対応してる。
・顧客向けのプロモーションコードを使用する
決済時にプロモーションコードを入力できるようにするかも、
checkout_sessions
を作成するときに設定する。
allow_promotion_codes: true
を指定すればOK。
const docRef = await db .collection('customers') .doc(currentUser.uid) .collection('checkout_sessions') .add({ price: 'price_1GqIC8HYgolSBA35zoTTN2Zl', allow_promotion_codes: true, success_url: window.location.origin, cancel_url: window.location.origin, });
Applying promotion codes programmatically
プロモーションコードをユーザに入力してもらうのではなく、プログラム側で適用する方法。
これもcheckout_sessions
を作成するときに設定。
promotion_code
にプロモーションコードを指定すればOK。
この方法だと、プロモーションコードにアクセスできる全員が利用できるので注意。
有効期限や利用できる商品/顧客を制限しているかなど確認するといい感じ。
const docRef = await db .collection('customers') .doc(currentUser.uid) .collection('checkout_sessions') .add({ price: 'price_1GqIC8HYgolSBA35zoTTN2Zl', allow_promotion_codes: true, success_url: window.location.origin, cancel_url: window.location.origin, });
Applying tax rates dynamically
動的税率を利用する方法。
Stripeのドキュメントだとこのあたり。
・Checkout に税率を追加する | 定期支払いに税を適用する
これもcheckout_sessions
を作成するときに設定。
dynamic_tax_rates
に税率IDを指定する。
const docRef = await db .collection("customers") .doc(currentUser) .collection("checkout_sessions") .add({ line_items: [ { price: "price_1HCUD4HYgolSBA35icTHEXd5", quantity: 1, dynamic_tax_rates: ["txr_1IJJtvHYgolSBA35ITTBOaew", "txr_1Hlsk0HYgolSBA35rlraUVWO", "txr_1HCshzHYgolSBA35WkPjzOOi"], }, ], success_url: window.location.origin, cancel_url: window.location.origin, });
Applying static tax rates
固定税率を利用する方法。
Stripeのドキュメントだとこのあたり。
・Checkout に税率を追加する | 定期支払いに税を適用する
これもcheckout_sessions
を作成するときに設定。
tax_rates
に税率IDを指定する。最大5つまで。
const docRef = await db .collection('customers') .doc(currentUser) .collection('checkout_sessions') .add({ price: 'price_1GqIC8HYgolSBA35zoTTN2Zl', tax_rates: ['txr_1HCjzTHYgolSBA35m0e1tJN5'], success_url: window.location.origin, cancel_url: window.location.origin, });
Collecting a shipping address during checkout
決済時に配送先住所を収集する方法。
これもcheckout_sessions
を作成するときに設定。
collect_shipping_address: true
を指定する。
さらに、この機能を利用するためには、
- productsコレクション配下に、shipping_countriesドキュメントを用意して、
- allowed_countriesフィールドで国を指定する必要がある
らしい。
サンプルコードには、
const docRef = await db .collection("customers") .doc(currentUser.uid) .collection("checkout_sessions") .add({ collect_shipping_address: true, price: "price_1GqIC8HYgolSBA35zoTTN2Zl", success_url: window.location.origin, cancel_url: window.location.origin, });
と、例が書いてあるが、allowed_countriesフィールドが無い...
Stripeのドキュメントだとこのあたり?
・Create a Checkout session with a shipping rate | Add shipping
Setting metadata on the subscription
チェックアウトセッションのメタデータをプログラム側で設定する方法。
メタデータはStripeのDashboardで検索できるので便利らしい。
const docRef = await db .collection('customers') .doc(currentUser) .collection('checkout_sessions') .add({ price: 'price_1GqIC8HYgolSBA35zoTTN2Zl', success_url: window.location.origin, cancel_url: window.location.origin, metadata: { item: 'item001', }, });
Adding multiple prices, including one-time setup fees
複数の価格を一度に決済する方法。
サブスク(定期支払)と1回限り(一括)の価格を一緒に使えるらしい。
初回のみのセットアップ費用などで使うっぽい。
checkout_sessions
を作成するときにline_items
も指定すればOK。
const docRef = await db .collection('customers') .doc(currentUser) .collection('checkout_sessions') .add({ line_items: [ { price: 'price_1HCUD4HYgolSBA35icTHEXd5', // サブスクの価格ID quantity: 1, tax_rates: ['txr_1HCjzTHYgolSBA35m0e1tJN5'], }, { price: 'price_1HEtgDHYgolSBA35LMkO3ExX', // 1回限りの価格ID quantity: 1, tax_rates: ['txr_1HCjzTHYgolSBA35m0e1tJN5'], }, ], success_url: window.location.origin, cancel_url: window.location.origin, });
ただし、複数のサブスク価格を指定する場合は注意が必要
- 複数のサブスク価格を指定すると、Firestoreですべてが配列に追加される
- ただ、price属性は、配列の先頭の価格と一緒になる
- さらに、現在のカスタマーポータルは複数のサブスクには対応してない
- この場合だとサブスクをキャンセルする機能だけ提供される
なので、複数のサブスクは指定しないほうがよさそう。
Collecting one-time payments without a subscription
1回限りの支払いを利用する方法。
checkout_sessions
を作成するときに1回限りの価格IDを指定するのと合わせて、
mode: "payment"
も指定する必要がある。
const docRef = await db .collection("customers") .doc(currentUser.uid) .collection("checkout_sessions") .add({ mode: "payment", price: "price_1GqIC8HYgolSBA35zoTTN2Zl", // 1回限りの価格ID success_url: window.location.origin, cancel_url: window.location.origin, });
サブスクの場合はsubscriptions
コレクションに保存されたが、
1回限りの場合はpayments
コレクションに保存される。
(サブスクと1回限りでは違うコレクションに保存される)
また、Firestoreに同期するために、
Webhookで以下の4つのイベントも送信するように変更が必要
payment_intent.succeeded payment_intent.payment_failed payment_intent.canceled payment_intent.processing
デフォルトがサブスク向けなので、1回限りも使うとなるといろいろ対応が必要そう。
Start a subscription via the Stripe Dashboard or API
v0.1.7からStripeのDashboardやAPI経由など、
Stripe Checkout経由で作成されてないサブスクも同期してくれるらしい。
(最新版だとあまり関係ない話。)
同期を有効にするためには、Airebase Authenticationのユーザを
Stripeの顧客&Firebaseの顧客コレクションの同期が必要。
Get the customer’s subscription
サブスクの詳細の保存場所について。
customers/{uid}/subscriptions
に保存されていて、
有効なものはstatus
をチェックすればOK。
db.collection('customers') .doc(currentUser.uid) .collection('subscriptions') .where('status', 'in', ['trialing', 'active']) .onSnapshot(async (snapshot) => { // In this implementation we only expect one active or trialing subscription to exist. const doc = snapshot.docs[0]; console.log(doc.id, ' => ', doc.data()); });
Redirect to the customer portal
カスタマーポータルのURLを取得する方法。
ext-firestore-stripe-subscriptions-createPortalLink
という関数が
Functionsに追加されているので、それを呼び出せばOK。
const functionRef = firebase .app() .functions('asia-northeast1') .httpsCallable('ext-firestore-stripe-subscriptions-createPortalLink'); const { data } = await functionRef({ returnUrl: window.location.origin }); window.location.assign(data.url);
Delete User Data
ユーザデータの削除について。
拡張機能設定の「Automatically delete Stripe customer objects」を
Auto Deleteにするといいよって書いてある。
Auto Deleteにすると、Firebase Authenticationのユーザが削除されたときに、
Stripe側の顧客オブジェクトも削除され、さらにサブスクもキャンセルになる。
退会時にFirebase Authenticationのユーザを削除すれば、
サブスクも全部解約してくれるので便利。
ただ、Firestoreから顧客ドキュメントを削除されないので、
別の拡張機能(Delete User Data)を使うと便利っぽい。
Monitoring
拡張機能のアクティビティをモニタリングする方法。
Firebaseコンソールで確認できるよう。
Firebaseのドキュメントをみると詳しく書いてあるとのこと。
・インストールした Firebase Extensions の管理
まとめ(再掲)
Firestoreの構造はこんな感じ。(コレクション名はデフォルト)
- customers/{uid} ... 顧客 - checkout_sessions/{id} ... 顧客のチェックアウトセッション - subscriptions/{id} ... 顧客のサブスク - payments/{id} ... 顧客の一回限り - products/{id} ... 商品 - prices/{id} ... 価格 - products/tax_rates - tax_rates/{id} ... 税率
大体の流れ。
1. 決済の流れ
- 商品の情報は
products/{id}
やprices/{id}
を取得 - 決済準備のときに、
checkout_sessions/{id}
を作成 - 準備ができたら、Stripe.jsでURLを生成
- 諸々の設定は
checkout_sessions/{id}
作成時にする
2. 購入状態の確認
- サブスクは
subscriptions/{id}
をみる - 一回限りは
payments/{id}
をみる - メタデータを使えば、Firabase Authのカスタムクレームでも可
3. 解約などはカスタマーポータル
- URLは用意されたFunctionsで生成
こんな感じ。以上!!
なんとなく、雰囲気は掴んできたけど、
ドキュメントの構造やWebhookのイベントがもやもやするので、
次回はそのあたりを見ていく予定(´ω`)
Stripe試してみたシリーズ
- 第一回: サンプルを試す
- 第二回: 拡張機能のドキュメントを読む
- 第三回: 拡張機能のソースを読む
- 第四回: Nuxtアプリで試してみる
- 第五回: 本番運用する前に