Firebaseを使ったサービスでStripeとRevenueCatを共存させる方法
Firebaseを使ったサービスにおける、StripeとRevenueCatの共存方法紹介したいと思います。
背景
Stripeはクレジットカード決済を実装しやすくしてくれるSaaSで、RevenueCatはアプリ内課金を実装しやすくしてくれるSaaSです。
Stripeでのクレジットカード決済をアプリに実装して審査に出すとリジェクトされることが増えました。デジタル課金やそれと判断されるアプリはIn-App-Purchaseを実装しないと審査を通してくれません。
また、webでしか入会できず、アプリはログインして使うようなアプリは見逃されてきましたが、昨今iOSアプリの審査が厳しくなり、In-App-Purchaseを実装しないとリリースさせてくれない例も出てきました。
ということで、もしモバイルアプリをリリースしたいのなら、StripeだけじゃなくてRevenueCatもサービスに実装することが求められてきているわけです。
実装方針
大きく分けて2パターンの実装方法があると思います。
- ①RevenueCatに「iOS App」と「Android App」と「Stripe App」を登録して、RevenueCatで一元管理する
- ②StripeとRevenueCatの購入イベントを別々に処理して、アプリの方で抽象クラスを作っていい感じに吸収する
一見すると①が良さそうなのですが、デメリットがあります。それはRevenueCatは1,000ドル/月ごとに8ドルかかる点です。StripeをRevenueCatに接続しただけで、Stripeの売上も今後含まれるようになり、売り上げれば売り上げるほどRevenueCat税を取られることになります。
ということで、今回は②にすることにしました。
ちなみに①をやるために、stripeとRevenueCatを同期する方法も以下の記事に書きましたのでご確認ください。
それでは、②の手順を軽く以下に紹介していきたいと思います。
FirebaseExtensionを入れる
StripeもRevenueCatもFirebaseExtensionを使ってusersというcollectionにstripeの決済データを同期します。
RevenueCatはStarterプランじゃないとFirebaseExtension使えませんが、Starterプランであっても月1000ドル行かなければ課金されないので、儲かるまでは大丈夫です。
Stripeでは、決済した際、usersコレクション配下のsubscriptionsというサブコレクションにデータが保存されます。
RevenueCatでは、決済した際、usersコレクションにsubscriptionsというMapが追加されます。product_idがkeyとなり追加されます。
また、revenueCatの取引イベントをcollectionに保存もしてくれます。今回、revenue_cat_events
という名前でcollectionに保存されるようにしてみました。typeのparameterを見れば、何が行われているかが大体わかります。最初の購入はINITIAL_PURCHASEです。
【Flutter】サブスクの抽象クラスを作り、StripeとRevenueCatそれぞれのサブスククラスはそれを継承する
Flutterの実装の話になります。以下のような抽象クラスを作りました。
abstract class AbstractSubscription {
// TODO: 必要に応じてstripeSubscriptionとrevenueCatSubscriptionの共通メソッドを生やす
bool get isErrorStatus;
int get amount;
String? get planName;
String? get planImageUrl;
String? get planDescription;
DateTime? get created;
}
そして、StripeとRevenueCatのサブスククラスは以下のようにそれを継承します。
class RevenueCatSubscription implements AbstractSubscription {
// ...
}
class StripeSubscription implements AbstractSubscription {
// ...
}
firestoreからサブスクをどっちも取得して、取れた方を返すという関数を実装します。
Future<AbstractSubscription?> fetchFirstSubscription() async {
AbstractSubscription? subscription;
// 割愛しますが、これは`users/{userId}/subscription/{subscriptionId}`というsubcollectionからドキュメントを取る関数です
final stripeSubscription = await _fetchStripeSubscriptions();
if (stripeSubscription != null) {
subscription = stripeSubscription;
} else {
// revenueCatにより作られたsubscriptionを参照する
final user = await _fetchMyUser();
subscription = user?.revenueCatSubscriptions.firstWhereOrNull(
(subscription) => subscription.unsubscribeDetectedAt == null,
);
}
return subscription;
}
このように抽象化できれば、アプリのコードにおいて、RevenueCatかStripeかというのを意識せずにコードを書くことが可能になります。
【Firebase Functions】サブスク作成をトリガーして入会処理を行う
以下の2つのトリガー関数をFirebase Functionsに実装しました。
- ①
users/{userId}/subscription/{subscriptionId}
がつくられた時の関数 - ②
revenue_cat_events/{eventId}
がつくられた時の関数
// ①
export const onCreateStripeSubscription = functions
.region('asia-northeast1')
.firestore.document('/users/{userId}/subscriptions/{subscriptionId}')
.onCreate(async (snapshot, context) => {
// ここに入会処理
});
// ②
export const onCreateRevenueCatSubscription = functions
.region('asia-northeast1')
.firestore.document('/revenue_cat_events/{eventId}')
.onCreate(async (snapshot, context) => {
// ここに入会処理
});
Flutter大学では、この関数内でslackの招待メールの送信や、GitHub Organizationへの招待などを行っています。
まとめ
まあ、そんな感じで、StripeとRevenueCatをなんとか共存させれたのではないでしょうか。
Flutter大学は、これらをうまく共存して運用しております!
Discussion