Open11

🐈【Flutter】RevenueCatでサブスクを実装【備忘録】

sho5sho5

▼公式 (QuickStart)
https://www.revenuecat.com/docs/getting-started

主な流れ (Quickstartの手順)

  1. RevenueCatのアカウント登録
  2. 新規プロジェクトの作成
  3. アプリの登録(iOS, Android)
  4. 各アプリストアで課金設定 (App Store Connect、Google Play Console)
  5. RevenueCatに戻り課金商品の設定
  6. SDKのインストール

7. SDKの設定 (ここから実装処理)

import 'dart:io' show Platform;

Future<void> initPlatformState() async {
  // ログの有効化
  await Purchases.setDebugLogsEnabled(true);
  
  // SDKの設定
  PurchasesConfiguration configuration;
  if (Platform.isAndroid) {
    configuration = PurchasesConfiguration("public_google_sdk_key");
  } else if (Platform.isIOS) {
    configuration = PurchasesConfiguration("public_ios_sdk_key");
  }
  await Purchases.configure(configuration);
}

8. 利用可能なProductsの表示

try {
  Offerings offerings = await Purchases.getOfferings();
  if (offerings.current != null) {
    // 現在のofferingを"offerings.current"で表示する
  }
} on PlatformException catch (e) {
    // 例外処理
}

9. 購入処理

try {
  // 購入処理
  CustomerInfo customerInfo = await Purchases.purchasePackage(package);

  // 購入の確認 (trueなら購入完了)
  var isPro = customerInfo.entitlements.all["my_entitlement_identifier"].isActive;
  if (isPro) {
    // 購入者であれば権利を付与する処理(広告非表示など)
  }

// 例外処理
} on PlatformException catch (e) {
  var errorCode = PurchasesErrorHelper.getErrorCode(e);
  if (errorCode != PurchasesErrorCode.purchaseCancelledError) {
    showError(e);             
  }
}

10. 購入状況を確認する

try {
  // 購入状況の確認
  CustomerInfo customerInfo = await Purchases.getCustomerInfo();
  if (customerInfo.entitlements.all["my_entitlement_identifier"].isActive) {
    // 購入済みの処理を実施
  }
} on PlatformException catch (e) {
  // 例外処理
}

11. リストア (購入履歴の復元)

try {
  // ...復元されたcustomerInfoをチェックし、entitlementが現在有効であるかどうかを確認する。
  CustomerInfo restoredInfo = await Purchases.restoreTransactions();
} on PlatformException catch (e) {
  // 例外処理
}

【オプション】リスナーの設定

Purchases.addCustomerInfoUpdateListener((customerInfo) => {
  // 購入情報が更新された際にイベントを受け取る(サブスク未登録→登録、登録→解約など)
});

◆公式のサンプルアプリ

https://github.com/RevenueCat/purchases-flutter/tree/main/revenuecat_examples/MagicWeather

◆RevenueCatのFlutterプラグインはこちら

https://pub.dev/packages/purchases_flutter

sho5sho5

サブスクリプションのテスト(iOS)

サブスク履歴の消去方法

① RevenueCatのSandBoxユーザ履歴から、ユーザを削除
② Xcodeで、シミュレーター起動時にタブからTransactionを削除する。
③ 実機の場合は追加手順として、APPstoreのSandBoxテスターから購入履歴を削除
https://dedede.blog/blog-tips/revenucat_sandobox_delete/

https://help.apple.com/app-store-connect/#/dev7e89e149d

https://www.revenuecat.com/docs/apple-app-store

sho5sho5

【RevenueCat】ユーザー識別に関して (ユーザーID)【Flutter】

#Flutter

予めユーザーIDを指定する事で、異なるデバイスでのログインでもサブスクを共有できるようになるらしい。
RevenueCatのダッシュボード上でユーザーを参照する事が可能になるので便利。

何も指定しない場合はランダムなIDが発行される。

https://www.revenuecat.com/docs/user-ids

▼ユーザーIDを指定する方法

// ユーザーIDを指定しない場合
await Purchases.configure(PurchasesConfiguration("public_sdk_key"));

// ユーザーIDを指定する場合
await Purchases.configure(
    PurchasesConfiguration("public_sdk_key")
      ..appUserID = "my_app_user_id"
);

▼後からユーザーIDを更新する事も可能(ログイン時など)

// アプリ起動時はユーザーIDを指定しない(ランダムなIDとなる)
await Purchases.configure(PurchasesConfiguration("public_sdk_key"));

//...

// ログイン後にユーザーIDを更新
LogInResult result = await Purchases.logIn("my_app_user_id");

※ログアウトする場合はは logOut()メソッドを呼び出す必要があります。

sho5sho5

【RevenueCat】サブスク状況の確認【Flutter】

本スクラップは以下を翻訳したもの
https://www.revenuecat.com/docs/customer-info

ユーザー情報の取得

CustomerInfoオブジェクトは、ユーザーについて利用可能なすべての購入および購読データを含んでいます。このオブジェクトは、購入やリストアが発生するたびに更新され、アプリのライフサイクルを通じて定期的に更新されます。最新の情報は、getCustomerInfo()を呼び出すことで常に取得することができます。

try {
  // 最新のcustomerInfoにアクセス
  CustomerInfo customerInfo = await Purchases.getCustomerInfo();
} on PlatformException catch (e) {
  // サブスク情報取得時のエラー処理
}

ユーザが購読しているかどうかを確認する

ユーザーの購読状況は、CustomerInfoオブジェクトとEntitlementInfoオブジェクトで簡単に確認できます。

1つのエンタイトルメントしか持たない多くのアプリでは、エンタイトルメントIDのisActiveステータスをすぐに確認できます。

if (customerInfo.entitlements.all["my_entitlement_identifier"].isActive) {
  // ユーザー "サブスク特典" を付与する処理
}

アプリに複数のentitlementsがあり、ユーザーが少なくとも1つのentitlementsに加入しているかどうかを確認する必要がある場合、EntitlementInfoオブジェクトのactiveな辞書でentitlement IDを確認することもできます。

if (customerInfo.entitlements.active.isNotEmpty) {
  //user has access to some entitlement
}

購入が行われず、トランザクションが同期されていない場合、CustomerInfo は空になることに注意することが重要です。つまり、RevenueCat ダッシュボードで entitlements が設定されていても、CustomerInfo には存在しない可能性があります。

CustomerInfoのアップデートを Listening する

Purchases SDK はあらゆるプラットフォームでシームレスに動作するため、ユーザーの CustomerInfo は様々なソースから変更される可能性があります。オプションのデリゲートメソッド purchases:receivedUpdated: に従えば、CustomerInfo の変更に対応することができます。これは、現在のデバイスで CustomerInfo の変更を受信したときに起動され、起動時およびアプリのライフサイクルを通じて呼び出されることを期待する必要があります。

CustomerInfo の更新は、RevenueCat バックエンドからアプリにプッシュされることはなく、更新は RevenueCat へのネットワークリクエストによってのみ行われます。

アプリによっては、デリゲートを無視し、次にアプリを起動したときに顧客情報の変更を処理するだけで十分な場合があります。あるいは、新しい CustomerInfo オブジェクトを要求するように、アプリ全体で処理することもできます。

Purchases.addCustomerInfoUpdateListener((info) {
    // customerInfo へのあらゆる変更を処理する
});

払い戻しへの対応

RevenueCat は、サブスクリプションと非サブスクリプションの両方の製品について、すべてのプラットフォームで返金を処理できます。また、RevenueCat が返金を検出するとすぐに、正しい権利ステータスを反映するように CustomerInfo が更新されますので、お客様側でのアクションは必要ありません。返金についてご質問がある場合は、コミュニティの記事をご覧ください。

sho5sho5

【RevenueCat】サブスク加入者の属性【Flutter】

本スクラップは以下を翻訳したもの
https://www.revenuecat.com/docs/subscriber-attributes

サブスクライバー属性は、ユーザーに関する追加の構造化された情報を格納するのに便利です。例えば、ユーザーのメールアドレスや追加のシステム識別子を RevenueCat に直接保存することができます。属性は、あなたが自分で明示的に表示することを選択しない限り、ユーザーには表示されません。

属性の設定

サブスクライバの属性は、共有された Purchases インスタンスの setAttributes() メソッドに文字列の辞書を渡すことで、SDK を通じて設定することができます。

Purchases.setAttributes({ "age" : "24", "custom_group_id" : "abc123" });

制限事項

属性は最大50個まで指定でき、キー名は最大40文字、値は最大500文字までです。キーは、以下の予約属性のいずれかに該当する場合を除き、$で始めることはできません。

属性キーのチェックリスト

✅ キーにホワイトスペースが含まれていないこと。
✅ キーが、非予約属性の場合は文字で、予約属性の場合は"$"で始まること。
✅ キーに-と_以外の非英数字が含まれていないこと。
✅ キーが40文字以下であること。
✅ 値は500文字以下
✅ カスタム属性は50個以下であること

予約属性

$ で始まる属性キーは、RevenueCatのために予約されています。現在予約されているキーの一覧は以下の通りです。

予約属性は、キーを設定することで直接書き込むか(プレフィックス $ を忘れないでください)、特別なヘルパーメソッドを使用して書き込むことができます。

Purchases.setEmail("test@example.com")
Purchases.setPhoneNumber("+16505551234")
Purchases.setDisplayName("John Appleseed")

プッシュトークンの設定

プッシュトークンは、Apple apnsやGoogleクラウドメッセージングを通じてユーザーとエンゲージするために使用することができます。これらは、ユーザーがアプリでプッシュ通知の許可を受けた後、システムコールバックを通じて RevenueCat に保存することができます。

Purchases.setPushToken(deviceToken);

属性の削除

NULLまたは空文字列をキーとして渡すことで、どの属性も消去することができます。また、カスタマービューで特定のユーザーの属性をクリアすることも可能です。

Purchases.setAttributes({"age" : ""});

属性の閲覧

秘密鍵を使用したREST API、Webhooks、および分析統合(Amplitude、Mixpanel、Segment)を通じて、購読者の属性にアクセスすることができます。カスタマービューダッシュボードには、編集可能な個々のユーザーの属性のリストも表示されます。

sho5sho5

【RevenueCat】Webhooks

本スクラップは以下を翻訳したもの
https://www.revenuecat.com/docs/webhooks

Webhooksからのサーバー間通知の強化

RevenueCat は、アプリ内でイベントが発生すると、いつでも通知を送信することができます。これは購読や購入のイベントに有効で、購読者の状態変化を監視し、それに応じて対応することができます。

Webhooksを統合すると、以下のことが可能になります。

アプリの購読者が退会する際に、アプリのメリットを再確認したり、課金に問題がある場合に知らせたりすることができます。

WebhookのURLを登録する

  1. RevenueCat ダッシュボードでプロジェクトに移動し、左メニューで「Integrations」カードを見つけます。新規]を選択します。

  2. Integrations メニューから 'Webhooks' を選択します。

  3. Webhooksを受信するエンドポイントのHTTPS URLを入力します。

  4. (オプション) 各POSTリクエストで送信されるauthorization(認証)ヘッダーを設定します。

RevenueCat はあなたのサーバーに POST リクエストを送信し、その本文は通知の JSON 表現になります。あなたのサーバーは200のステータスコードを返す必要があります。それ以外のステータスコードは、当社のバックエンドで失敗とみなされます。RevenueCatは、遅延を増やしながら(5分、10分、20分、40分、80分)、後で再試行します(最大5回)。5回リトライした後は、通知の送信を停止します。

Webhookを受信した場合は、誤ってタイムアウトの制限を超えないように、迅速に対応することが重要です。アプリは、応答が送信されるまで処理を延期することをお勧めします。

Webhook Events

Eventのtype(種類)

Webhookイベントのtype(種類) 概要 App Store Play Store Web
TEST RevenueCatのダッシュボードから発行されるテストイベント。
✔︎ INITIAL_PURCHASE (初期購入) 新規に契約された。
NON_RENEWING_PURCHASE (非更新の購入) お客様が自動更新されない購入をした。
✔︎ RENEWAL (更新) 既存の購読が更新された。これは、現在の請求期間の終了時、またはそれ以降に、失効したユーザーが再購読した場合に発生します。
PRODUCT_CHANGE 契約者が契約商品を変更した。
CANCELLATION 定期購入または更新不可の購入がキャンセルされました。詳しくは解約理由をご覧ください。
UNCANCELLATION 解約された有効期限内の利用権が再び有効になりました。
BILLING_ISSUE (課金問題) 購読者に課金しようとしたところ、問題が発生しました。これはサブスクリプションが期限切れになったことを意味するものではありません。CANCELLATION イベントをリッスンしている場合、安全に無視することができます。cancel_reason=BILLING_ERROR.
SUBSCRIBER_ALIAS 非推奨。既存の契約者に対し、新しい app_user_id が登録されました。
SUBSCRIPTION_PAUSED 購読が一時停止されています。
✔︎ TRANSFER (転送) あるApp User IDから別のApp User IDへの取引 (トランザクション)とエンタイトルメントの転送が開始されました。
EXPIRATION (期限切れ) サブスクリプションが期限切れになり、アクセスを削除する必要があります。Platform Server Notificationを設定している場合、このイベントは有効期限が切れたことが通知されるとすぐに(数秒から数分以内に)発生します。通知が設定されていない場合、約1時間の遅延が発生する可能性があります。

※ ✔︎は翻訳者が独自に付けたもの

Eventsの形式

Webhook イベントは、JSON でシリアライズされます。サーバーへの POST リクエストのボディには、シリアライズされたイベントと、API バージョンが含まれます。

JSON
{
  "api_version": "1.0",
  "event": {
    "aliases": [
      "yourCustomerAliasedID",
      "yourCustomerAliasedID"
    ],
    "app_id": "yourAppID",
    "app_user_id": "yourCustomerAppUserID",
    "country_code": "US",
    "currency": "USD",
    "entitlement_id": "pro_cat",
    "entitlement_ids": [
      "pro_cat"
    ],
    "environment": "PRODUCTION",
    "event_timestamp_ms": 1591121855319,
    "expiration_at_ms": 1591726653000,
    "id": "UniqueIdentifierOfEvent",
    "is_family_share": false,
    "offer_code": "free_month",
    "original_app_user_id": "OriginalAppUserID",
    "original_transaction_id": "1530648507000",
    "period_type": "NORMAL",
    "presented_offering_id": "OfferingID",
    "price": 2.49,
    "price_in_purchased_currency": 2.49,
    "product_id": "onemonth_no_trial",
    "purchased_at_ms": 1591121853000,
    "store": "APP_STORE",
    "subscriber_attributes": {
      "$Favorite Cat": {
        "updated_at_ms": 1581121853000,
        "value": "Garfield"
      }
    },
    "takehome_percentage": 0.7,
    "transaction_id": "170000869511114",
    "type": "INITIAL_PURCHASE"
  }
}

共通部分

Field 説明 使用可能な値
type String Type of the event. TEST / INITIAL_PURCHASE / NON_RENEWING_PURCHASE / RENEWAL / PRODUCT_CHANGE / CANCELLATION / BILLING_ISSUE / SUBSCRIBER_ALIAS / SUBSCRIPTION_PAUSED / TRANSFER
id String イベントのユニークな識別子。
app_id String イベントが関連するアプリのユニークな識別子。プロジェクト内のアプリに対応します。この値は、近日中にプロジェクト設定のアプリの設定ページで確認できるようになる予定です。
event_timestamp_ms Integer イベントが発生した時間。イベントのトリガーとなったアクション(購入、キャンセルなど)が発生した時刻と必ずしも一致しない。
✔︎ app_user_id String 契約者の最後に見たアプリのユーザーID。
✔︎ original_app_user_id String 契約者が最初に使用したアプリのユーザーID。
✔︎ aliases (通称) [String] 契約者がこれまでに使用したすべてのアプリのユーザーID。

サブスクリプションのライフサイクルイベントのフィールド

Field 説明 使用可能な値
product_id String サブスクリプションのプロダクト識別子。
entitlement_ids [String] サブスクリプションのエンタイトルメント ID。 product_id がどのエンタイトルメントにもマッピングされていない場合、NULL にすることができます。
entitlement_id String 非推奨。参照 entitlement_ids. 非推奨。参照 entitlement_ids.
period_type String 取引(トランザクション)の期間タイプ。 TRIAL:無料体験版, INTRO、入門用価格, NORMAL:標準的な購読, PROMOTIONAL、RevenueCatを通じて付与されたサブスクリプション用
✔︎ purchased_at_ms Integer 取引(トランザクション)が購入された時刻。Unixエポックからのミリ秒単位で測定。
grace_period_expiration_at_ms Integer BILLING_ISSUE イベントでのみ使用可能です。サブスクリプションの猶予期間が終了する時間。Unixエポックからのミリ秒単位で測定されます。このフィールドは、ユーザーが現在猶予期間中かどうかを判断するために使用します。 加入に猶予期間がない場合、NULLになることがあります。
✔︎ expiration_at_ms Integer 取引(トランザクション)の終了時間。Unixエポックからのミリ秒単位で測定される。サブスクリプションがまだアクティブであるかどうかを判断するために、このフィールドを使用します。 定期購入以外でもNULLにすることが可能です。
auto_resume_at_ms Integer Androidの購読が一時停止された後、再開される時間。Unixエポックからのミリ秒単位で測定されます。Play StoreサブスクリプションとSUBSCRIPTION_PAUSEDイベントでのみ利用可能です。
✔︎ store String サブスクリプションが属するストア。 AMAZON, APP_STORE, MAC_APP_STORE, PLAY_STORE, PROMOTIONAL, STRIPE
environment String ストアの環境 SANDBOX, PRODUCTION
is_trial_conversion Boolean RENEWALイベントのみ利用可能です。前回の取引(トランザクション)が無料トライアルであったか否かを問わない。 true or false
cancel_reason String CANCELLATIONイベントのみ利用可能です。キャンセルと有効期限切れの理由をご覧ください。 UNSUBSCRIBE, BILLING_ERROR, DEVELOPER_INITIATED, PRICE_INCREASE, CUSTOMER_SUPPORT, UNKNOWN
expiration_reason String EXPIRATIONイベントのみ利用可能です。[キャンセルと有効期限切れの理由](Cancellation and Expiration Reasons)を参照してください。
new_product_id String サブスクライバーが切り替えた新しい商品の商品識別子。App StoreサブスクリプションとPRODUCT_CHANGEイベントでのみ使用可能です。
presented_offering_id String レガシーエンタイトルメントを使用しているアプリでは使用できません。最初の購入時にユーザーに提示された提供物の識別子です。
price Double USDの取引価格。 価格が不明な場合はNULL、無料体験の場合は0を指定することができる。払い戻しの場合は負にすることができる。
currency String 製品が購入されたISO 4217の通貨コード。 USD, CAD, etc... 通貨が不明な場合はNULLでも可。
price_in_purchased_currency Double 商品が購入された通貨での取引価格。 価格が不明な場合はNULL、無料体験の場合は0を指定することができる。払い戻しの場合は負にすることができる。
tax_percentage Double 取引価格のうち、税金が差し引かれる割合の目安(国や店舗によって異なる)。 税率が不明な場合は NULL とすることができる。
commission_percentage Double 取引価格のうち、ストアコミッション/プロセシングフィーとして差し引かれた概算のパーセンテージ。 手数料のパーセンテージが不明な場合はNULLでもよい。
takehome_percentage Double DEPRECATED: 取引価格のうち、手数料の後、VATとDSTの税金を考慮する前に開発者に支払われる推定パーセンテージ。代わりにtax_percentageとcommission_percentageを使用して収益を計算することをお勧めします。詳しくはこちらをご覧ください。
subscriber_attributes 属性名と属性オブジェクトのマップ。詳細については、サブスクライバー属性ガイドを参照してください。
transaction_id String Apple/Amazon/Google/Stripeからの取引(トランザクション)識別子。
is_family_share Boolean ユーザーが購入したのか、家族共有で購入したのかを示す。 true or false。Apple以外で購入した場合は常にfalse。
transferred_from [String] このフィールドは、タイプがTRANSFERに設定されている場合のみ利用可能です。取引(トランザクション)とエンタイトルメントが取得され、transferred_toに付与されるApp User ID。
transferred_to [String] このフィールドは、タイプがTRANSFERに設定されている場合のみ利用可能です。transferred_fromから取得した取引(トランザクション)とエンタイトルメントを受け取るApp User ID。
country_code String 製品が購入されたISO 3166の国コード。アプリユーザーの所在地を示す2文字の国コード(US、GB、CAなど) (この国コードは、SDKが契約者に対して最後に見たリクエストから得られます。) US, CA, etc.
offer_code String このフィールドは、タイプがSUBSCRIBER_ALIASまたはTRANSFERに設定されている場合は使用できません。顧客が取引(トランザクション)の引き換えに使用したオファーコード。App StoreとPlay Storeで利用可能です。App Storeの場合、このプロパティはoffer_code_ref_nameに対応します。Play Storeの場合、このプロパティはpromotionCodeに対応します。 この製品にオファーコードが使用されていない場合は、NULLにすることができます。

解約・失効理由

理由 説明 App Store Play Store Web
UNSUBSCRIBE サブスクライバーが自発的にキャンセルされました。このイベントは、サブスクリプションが期限切れになったときではなく、ユーザーがサブスクリプションを解除したときに発生します。
BILLING_ERROR Apple、Amazon、または Google が、その支払い方法を使用して契約者に課金することができませんでした。課金に関する問題が検出されると、キャンセル理由 BILLING_ERROR を持つ CANCELLATION イベントが発生します。支払い猶予期間(設定されている場合)が支払いに回復せず終了し、顧客がサブスクリプションへのアクセスを失うと、有効期限切れ理由 BILLING_ERROR を持つ EXPIRATION イベントが発行されます。
DEVELOPER_INITIATED デベロッパーが解約した。
PRICE_INCREASE 契約者は値上げに同意していない。
CUSTOMER_SUPPORT お客様がAppleのサポートを通じてキャンセルして返金を受けた、Play StoreのサブスクリプションがRevenueCatを通じて返金された、AmazonのサブスクリプションがAmazonのサポートを通じて返金された、またはWebサブスクリプションが返金された。
UNKNOWN アップルは中止の理由を明らかにしなかった。
SUBSCRIPTION_PAUSED サブスクリプションが一時停止されたため、期限切れとなりました(EXPIRATIONイベントのみ)。

テスト

サーバー側の実装は、サンドボックスのサブスクリプションを購入するか、RevenueCat のダッシュボードからテスト用の Webhook イベントを発行してテストすることができます。

サンドボックス購入でテストする場合、環境値はSANDBOXになります。RevenueCat 自身はサンドボックス環境と本番環境を持たないので、この値はストアから受け取った取引(トランザクション)の種類によってのみ決定されます。RevenueCat の同じ顧客は、サンドボックスと本番の両方の取引(トランザクション)をアカウントに関連付けることができます。

サブスクリプションステータスの同期

Webhook は、複数のシステムで購読者のステータスを RevenueCat と同期させるために一般的に使用されます。異なる Webhook タイプを扱うロジックを単純化するために、RevenueCat から顧客の購読ステータスをデータベースに同期するための GET /subscribers REST API を使ったポーリングシステムを作成することをお勧めします。そして、各ウェブフックイベントは、この同期機能を呼び出すトリガーとするだけでよいのです。このアプローチにはいくつかの利点があり、システムをより堅牢でスケーラブルにすることができます。

セキュリティとベストプラクティス

認証

Webhook リクエストに使用する認証ヘッダーは、ダッシュボードで設定できます。サーバーは、通知ごとに認証ヘッダーの有効性を確認する必要があります。

レスポンス時間

サーバーが 60 秒以内に応答を終了しない場合、RevenueCat は切断します。その後、最大5回まで再試行します。アプリは迅速に応答し、応答が送信された後まで処理を延期することをお勧めします。

配信の遅延

ほとんどのWebhookは、通常イベント発生から5~60秒以内に配信されます。キャンセルイベントは、通常ユーザーがサブスクリプションをキャンセルしてから2時間以内に配信されます。アプリを設計する際には、これらの配信時間を意識しておく必要があります。

将来性

新しいイベントタイプを含め、ここで紹介した以外のフィールドを含むWebhookを処理できるようにする必要があります。今後、APIのバージョンを変更することなく、新しいフィールドやイベントの種類を追加する可能性があります。また、適切なAPIのバージョン管理や非推奨を行わずに、フィールドやイベントを削除することはありません。

Webhook Eventsのサンプル

以下は、RevenueCat から受け取る可能性のある Webhook の代表的なサンプルです。Webhookには、ここで紹介した以外にもフィールドが含まれることがあることを覚えておいてください。

Refund (払い戻し)
{
  "event" : {
    "event_timestamp_ms" : 1601337615995,
    "product_id" : "com.revenuecat.myapp.monthly",
    "period_type" : "NORMAL",
    "purchased_at_ms" : 1601258901000,
    "expiration_at_ms" : 1601336705000,
    "environment" : "PRODUCTION",
    "entitlement_id" : "pro",
    "entitlement_ids" : [
      "pro"
    ],
    "presented_offering_id" : "defaultoffering",
    "transaction_id" : "100000000000000",
    "original_transaction_id" : "100000000000000",
    "app_user_id" : "$RCAnonymousID:12345678-1234-ABCD-1234-123456789123",
    "aliases" : [
      "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
      "user_1234"
    ],
    "offer_code": null,
    "original_app_user_id" : "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "cancel_reason" : "CUSTOMER_SUPPORT",
    "currency" : "USD",
    "price" : -9.99,
    "price_in_purchased_currency" : -9.99,
    "subscriber_attributes" : {
      "$idfa" : {
        "value" : "12345678-1234-1234-1234-12345678912x",
        "updated_at_ms" : 1578018408238
      },
      "$appsflyerId" : {
        "value" : "1234567891234-1234567",
        "updated_at_ms" : 1578018408238
      }
    },
    "store" : "APP_STORE",
    "takehome_percentage" : 0.7,
    "tax_percentage": 0.1109,
    "commission_percentage": 0.15,
    "type" : "CANCELLATION",
    "id" : "12345678-1234-1234-1234-12345678912"
  },
  "api_version" : "1.0"
}
Billing Issue (請求に関するIssue)
{
  "event" : {
    "event_timestamp_ms" : 1601337601013,
    "product_id" : "com.revenuecat.myapp.monthly",
    "period_type" : "NORMAL",
    "purchased_at_ms" : 1598640647000,
    "expiration_at_ms" : 1601319047000,
    "environment" : "PRODUCTION",
    "entitlement_id" : "pro",
    "entitlement_ids" : [
      "pro"
    ],
    "presented_offering_id" : "defaultoffering",
    "transaction_id" : "100000000000002",
    "original_transaction_id" : "100000000000000",
    "app_user_id" : "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "aliases" : [
      "$RCAnonymousID:12345678-1234-1234-1234-123456789123"
    ],
    "offer_code": "summer_special",
    "original_app_user_id" : "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "currency" : "USD",
    "price" : 0,
    "price_in_purchased_currency" : 0,
    "subscriber_attributes" : {
      "$idfa" : {
        "value" : "12345678-1234-1234-1234-12345678912x",
        "updated_at_ms" : 1578018408238
      },
      "$appsflyerId" : {
        "value" : "1234567891234-1234567",
        "updated_at_ms" : 1578018408238
      }
    },
    "store" : "APP_STORE",
    "takehome_percentage" : 0.7,
    "tax_percentage": null,
    "commission_percentage": null,
    "type" : "BILLING_ISSUE",
    "id" : "12345678-1234-1234-1234-12345678912"
  },
  "api_version" : "1.0"
}
Product change (製品変更)
{
  "event" : {
    "event_timestamp_ms" : 1601338594769,
    "product_id" : "com.revenuecat.myapp.monthly",
    "period_type" : "NORMAL",
    "purchased_at_ms" : 1601304429682,
    "expiration_at_ms" : 1601311606660,
    "environment" : "PRODUCTION",
    "entitlement_id" : "subscription",
    "entitlement_ids" : [
      "subscription"
    ],
    "presented_offering_id" : "defaultoffering",
    "transaction_id" : "GPA.1234-1234-1234-12345",
    "original_transaction_id" : "GPA.1234-1234-1234-12345",
    "app_user_id" : "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "aliases" : [
      "$RCAnonymousID:12345678-1234-1234-1234-123456789123"
    ],
    "offer_code": null,
    "original_app_user_id" : "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "new_product_id" : "com.revenuecat.myapp.yearly",
    "currency" : "USD",
    "price" : 0,
    "price_in_purchased_currency" : 0,
    "subscriber_attributes" : {},
    "store" : "PLAY_STORE",
    "takehome_percentage" : 0.70,
    "type" : "PRODUCT_CHANGE",
    "id" : "12345678-1234-1234-1234-12345678912"
  },
  "api_version" : "1.0"
}
Unsubscribe (退会)
{
  "event": {
    "event_timestamp_ms": 1601337615995,
    "product_id": "com.revenuecat.myapp.weekly",
    "period_type": "NORMAL",
    "purchased_at_ms": 1601417766000,
    "expiration_at_ms": 1602022566000,
    "environment": "PRODUCTION",
    "entitlement_id": "pro",
    "entitlement_ids": [
      "pro"
    ],
    "presented_offering_id": "defaultoffering",
    "transaction_id": "100000000000002",
    "original_transaction_id": "100000000000000",
    "app_user_id": "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "aliases": [
      "$RCAnonymousID:12345678-1234-ABCD-1234-123456789123",
      "user_1234"
    ],
    "offer_code": "free_month",
    "original_app_user_id": "$RCAnonymousID:12345678-1234-ABCD-1234-123456789123",
    "cancel_reason": "UNSUBSCRIBE",
    "currency": "USD",
    "price": 0.0,
    "price_in_purchased_currency": 0.0,
    "subscriber_attributes": {
      "$idfa": {
        "value": "12345678-1234-1234-1234-12345678912x",
        "updated_at_ms": 1578018408238
      },
      "$appsflyerId": {
        "value": "1234567891234-1234567",
        "updated_at_ms": 1578018408238
      },
      "favorite_food": {
        "value": "pizza",
        "updated_at_ms": 1578018408238
      }
    },
    "store": "APP_STORE",
    "takehome_percentage": 0.7,
    "type": "CANCELLATION",
    "id": "12345678-ABCD-1234-ABCD-12345678912"
  },
  "api_version": "1.0"
}
Transfer (転送)
{
 "event": {
     "type": "TRANSFER",
     "id": "CD489E0E-5D52-4E03-966B-A7F17788E432",
     "store": "APP_STORE",
     "transferred_from": ["00005A1C-6091-4F81-BE77-F0A83A271AB6"],
     "transferred_to": ["4BEDB450-8EF2-11E9-B475-0800200C9A66"],
     "event_timestamp_ms": 78789789798798
 },
 "api_version": "1.0"
}
sho5sho5

【RevenueCat】Firebase

本スクラップは以下を翻訳したもの
https://www.revenuecat.com/docs/firebase-integration

RevenueCatのアプリ内課金イベントをFirebaseと連携させる

この拡張機能は、Apple App Store、Google Play Store、Amazon Appstore でのアプリ内課金のための RevenueCat バックエンドとして Firebase サービスを使用し、プレミアムコンテンツへのアクセス制御と顧客の購入情報の Firestore への同期を行います。例えば、以下のようなことが考えられます。

  • 購入ライフサイクルイベント(トライアル開始、購入、サブスクリプション更新、課金問題など)をFirestoreに保存し、それらに対応する。
  • Firestoreに顧客とその購入品に関する情報を保存し、更新することができます。
  • 顧客のエンタイトルメントに関する情報をFirebase Authentication Custom Claimsとして更新します。

このFirebaseインテグレーションは、2つのパートがあり、それぞれ独立して使用することができます。Google AnalyticsとFirebase Extensionです。この統合のGoogle Analyticsの部分は、RevenueCatがサブスクリプションのライフサイクルイベントをFirebase Analytics / Google Analyticsに送信することを可能にします。Firebase Extensionは、RevenueCatがCloud Firestoreコレクションに顧客情報を保存・更新し、ユーザーの認証トークンにカスタムクレームを設定してアクティブなエンタイトルメントステータスをチェックできるようにするものです。

統合の各部分には追加のセットアップが必要で、以下の表に概要が示されています。

統合 必要なもの
Google Analytics ✅ $firebaseAppInstanceId 契約者属性、❌(任意、ただし強く推奨)FirebaseのユーザーIDを設定する。
Firebase Extension ✅ FirebaseのユーザーIDを設定する

1. プロジェクトにFirebaseサービスをセットアップする

この拡張機能をインストールする前に、Firebaseプロジェクトで以下のFirebaseサービスを設定してください。

  • (オプション) In-App Purchase & Subscriptions の詳細を保存するための Cloud Firestore。
    • ドキュメントの手順に従い、Cloud Firestoreデータベースを作成します。
  • (オプション) Firebase Authenticationにより、Custom Claimsの管理を可能にするために、ユーザーに対して異なるサインアップオプションを有効にします。
    • Firebaseコンソールで、ユーザーに提供したいサインイン方法を有効化します。

2. RevenueCatでFirebaseのUser Identityを設定する

RevenueCat で Firebase のユーザー ID を設定する際には、Firebase UID を RevenueCat アプリのユーザー ID として使用するようにしてください。このステップはオプションですが、この統合のGoogle Analytics部分のベストプラクティスとして強く推奨します。Firebase Extension の部分は、このステップを完了する必要があります。

import FirebaseAuth
import RevenueCat

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // Configure Purchases before Firebase
    Purchases.configure(withAPIKey: "public_sdk_key")
    Purchases.shared.delegate = self
    
    // Add state change listener for Firebase Authentication
    Auth.auth().addStateDidChangeListener { (auth, user) in
    
            
        if let uid = user?.uid {
            
            // identify Purchases SDK with new Firebase user
            Purchases.shared.logIn(uid, { (info, created, error) in
                if let e = error {
                    print("Sign in error: \(e.localizedDescription)")
                } else {
                    print("User \(uid) signed in")
                }
            })
        }
    }
    return true
}

3. Google Analyticsに解析イベントを送信する

加入者のライフサイクルイベントを Google Analytics に送信するには、ユーザーの加入者属性として $firebaseAppInstanceId を設定し、RevenueCat 統合設定ページより統合を有効にする必要があります。

サブスクライバー属性として $firebaseAppInstanceId を設定します。

Firebase AnalyticsのパッケージからアプリのインスタンスIDを取得していることを確認してください。

Swift
import FirebaseAuth
import RevenueCat

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // Configure Purchases before Firebase
    Purchases.configure(withAPIKey: "public_sdk_key")
    Purchases.shared.delegate = self

    // Set the reserved $firebaseAppInstanceId subscriber attribute from Firebase Analytics
    let instanceID = Analytics.appInstanceID();
    if let unwrapped = instanceID {
      print("Instance ID -> " + unwrapped);
        print("Setting Attributes");
      Purchases.shared.attribution.setFirebaseAppInstanceID(unwrapped)
     } else {
        print("Instance ID -> NOT FOUND!");
     }
    
    return true
}

Googleアナリティクスを有効化する

RevenueCatのダッシュボードから統合を「オン」にすることができます。

  1. Navigate to your project in the RevenueCat dashboard and find the Integrations card in the left menu. Select + New
    https://files.readme.io/2bd4b4b-9ba8b4c-Screen_Shot_2021-12-01_at_12.23.10_PM.png

  2. Choose Firebase from the integrations menu
    https://files.readme.io/41697af-Screen_Shot_2022-04-13_at_9.21.49_AM.png
    Firebase Google Analyticsの設定画面

  3. iOSアプリやAndroidアプリのFirebase App IDとAPIシークレットを追加します。
    Firebase App IDを設定するには、Google Analytics > Admin > Data Streams > iOS/Android > Add Streamに移動します。App Streamを開いて、Firebase App IDを探します。コピーして、RevenueCatの設定ページに貼り付けます。
    https://files.readme.io/a4ad055-Screen_Shot_2022-04-05_at_11.03.48.png
    データストリームの詳細ページ

APIシークレットを見つけるには、同じAppストリームの詳細ページで、「Measure Protocol API secrets」を選択します。APIシークレットを作成します。コピーして、RevenueCat の設定ページに貼り付けます。
https://files.readme.io/47ff07c-Firebase.png
計測プロトコルAPIの秘密ページ

  1. RevenueCatが売上を購入通貨(オリジナル通貨または米ドル)で報告するかどうかを選択します。
  2. 売上を総売上高(App Store手数料前)として報告するか、Store手数料および/または見積税後として報告するかを選択します。

"Add Integration" を忘れずに選択してください。

イベント

Firebase統合のGoogle Analytics部分は、以下のイベントをトラッキングします。

〜途中経過〜

sho5sho5

【iOS】Sandboxについて

Sandbox:サブスクリプション用のテストアカウント

▼Apple公式

https://developer.apple.com/jp/documentation/storekit/in-app_purchase/testing_in-app_purchases_with_sandbox/

https://help.apple.com/app-store-connect/?lang=ja#/dev7e89e149d