非自発的チャーンという問題
すべての解約が、離れたいと望んだ顧客によるものとは限りません。その大きな割合は、単なる支払いの失敗です。カードの有効期限切れ、残高不足、銀行による拒否などが原因です。ユーザーは解約を選んでいないのに、回復のしくみがなければ気づかないうちにアクセスを失い、収益も失われてしまいます。
RevenueCat の State of Subscription Apps 2026 によると、Google Play のサブスクリプション解約の約 3 分の 1 が、非自発的な請求失敗でした(App Store の割合のおよそ 2 倍です)。同レポートの言い方を借りれば、請求の修正は最も明確な成長のレバーの一つです。このコードラボでは、その方法を紹介します。
このコードラボで扱う内容は次のとおりです。
- App Store Connect と Google Play で請求の猶予期間を有効にし、ストアがカードをリトライする間もアクセスを継続させます。
- RevenueCat で請求リトライ中のアクティブなサブスクリプションを検知します(iOS、Android、React Native)。
- Customer Center またはストアの
managementURLで、ユーザーに支払い方法を修正してもらいます。 - BILLING_ISSUE Webhook に対応し、アプリの外でユーザーを再エンゲージします。
自発的チャーンと非自発的チャーン
まったく異なる 2 つの出来事が、どちらも「サブスクリプションが終了した」として表れます。両者は別々に扱いましょう。
| 種類 | 原因 | 取り戻す方法 |
|---|---|---|
| 自発的 | ユーザーが解約をタップ | 解約フローでのオファー、ウィンバック、価値のリマインド |
| 非自発的 | 更新時に支払いが失敗 | 猶予期間、請求リトライ、支払い修正の促し |
このコードラボが対象とするのは非自発的チャーンです。回復の連鎖はこうです。ストアは一定の期間(猶予期間)にわたってカードのリトライを続けます。その間、ユーザーはアクセスを保ちます。そしてアプリは、期間が終わる前に支払い方法を更新するよう促します。
unsubscribeDetectedAt が設定され、非自発的な失敗では billingIssueDetectedAt が設定されます。後者はステップ 6 で使います(自発的チャーンは別の Customer Center コードラボで扱います)。
App Store Connect で猶予期間を有効にする
請求の猶予期間を設定すると、Apple が失敗した更新をリトライする間もサブスクライバーはアクセスを保てます。Apple が期限内に支払いを回復できれば、サービスにも収益にも中断は生じません。
- App Store Connect でアプリを開き、サイドバーの Subscriptions をクリックします。
- Billing Grace Period セクションで Set Up Billing Grace Period をクリックします。
- 期間を選びます。3、16、28 日のいずれかです(アプリのすべてのサブスクリプションに適用されます)。
- 更新の種類(すべての更新、または有料から有料への更新のみ)と環境(サンドボックスのみ、または本番とサンドボックス)を選びます。
- Confirm をクリックします。
Google Play で猶予期間とアカウントの保留を有効にする
Google Play には 2 つの回復フェーズがあります。猶予期間中は、Google がカードをリトライする間もユーザーはアクセスを保ちます。それが失敗すると、サブスクリプションはアカウントの保留へ移り、Google がリトライを続ける間、ユーザーはアクセスを失います。どちらのフェーズでも回復できれば、サブスクリプションはそのまま継続します。
- Google Play Console で Monetize with Play → Products → Subscriptions に移動します。
- サブスクリプションを開き、自動更新の基本プランを選びます。猶予期間とアカウントの保留は基本プランごとに設定します。
- 猶予期間(更新の支払いが未解決の間、ユーザーがアクセスを保てる長さ)を設定します。
- アカウントの保留の長さ(アクセスを一時停止したあと、Google がリトライを続ける長さ)を設定します。
RevenueCat が請求の問題をどう表すか
検知をシンプルにする重要なポイントがこれです。サブスクリプションが猶予期間にある間、RevenueCat はエンタイトルメントを isActive: true のまま保ち(ユーザーはまだアクセスできます)、billingIssueDetectedAt に失敗が検知された時刻を設定します。
つまり、「支払っている顧客だが、カードが失敗したばかり」というシグナルは次のとおりです。
entitlement.isActive == true AND entitlement.billingIssueDetectedAt != null
=> active subscriber, in billing retry, nudge them to fix payment
エンタイトルメント上の関連フィールドは次のとおりです(名前はプラットフォーム間で共通です)。
isActive: 顧客が現在アクセスできるかどうか。猶予期間中はtrueのままです。billingIssueDetectedAt: 支払い失敗の検知時に設定され、支払いが回復すると解除される日時。問題がないときは null です。willRenew: サブスクリプションが更新される設定かどうか。unsubscribeDetectedAt: 自発的な解約で設定されます(請求の問題と混同しないでください)。
billingIssueDetectedAt が設定されています。Google がアカウントの保留へ移ると、エンタイトルメントはアクティブでなくなるため、通常のエンタイトルメントチェックがすでにアクセスをブロックします。
アプリ内で請求リトライを検知する
現在の CustomerInfo を読み取り、pro エンタイトルメントに請求の問題のシグナルがあるか確認します。
iOS (Swift)
let info = try await Purchases.shared.customerInfo()
if let pro = info.entitlements["pro"], pro.isActive {
if pro.billingIssueDetectedAt != nil {
// Active subscriber whose payment is failing: show a "fix payment" banner.
showBillingIssueBanner()
}
}
Android (Kotlin)
val info = Purchases.sharedInstance.awaitCustomerInfo()
val pro = info.entitlements["pro"]
if (pro?.isActive == true && pro.billingIssueDetectedAt != null) {
// Active subscriber in billing retry.
showBillingIssueBanner()
}
React Native (TypeScript)
const info = await Purchases.getCustomerInfo();
const pro = info.entitlements.active['pro'];
if (pro && pro.billingIssueDetectedAt != null) {
// Active subscriber in billing retry.
showBillingIssueBanner();
}
CustomerInfo の更新も購読して、支払いが回復した瞬間にバナーが消えるようにします(React Native のパターンは
CustomerInfo リスナーガイドを参照してください)。
Customer Center でユーザーに支払いを修正してもらう
問題を検知したら、ワンタップで修正できる手段をユーザーに用意します。方法は 2 つあります。
選択肢 A: Customer Center(推奨)
Customer Center は、そのまま組み込める RevenueCat のセルフサービス型サブスクリプション管理 UI です。ユーザーをサブスクリプションの管理と支払いの更新へ案内し、購入の復元、解約、リテンションオファーも扱います。
// iOS (SwiftUI) - RevenueCatUI
import RevenueCatUI
struct SettingsView: View {
@State private var showCustomerCenter = false
var body: some View {
Button("Manage Subscription") { showCustomerCenter = true }
.sheet(isPresented: $showCustomerCenter) {
CustomerCenterView()
}
}
}
// React Native - react-native-purchases-ui
import RevenueCatUI from 'react-native-purchases-ui';
await RevenueCatUI.presentCustomerCenter();
Android では、purchases-ui の CustomerCenter Composable を描画します。
選択肢 B: ストアの管理ページを直接開く
Customer Center を使わない場合は、customerInfo.managementURL でユーザーをストアのサブスクリプション管理ページへ直接送れます。この URL は、RevenueCat が正しいストア向けに自動で埋めてくれます。
// React Native
import { Linking } from 'react-native';
const info = await Purchases.getCustomerInfo();
if (info.managementURL) {
await Linking.openURL(info.managementURL);
}
iOS では customerInfo.managementURL を UIApplication.shared.open(_:) で開き、Android では ACTION_VIEW インテントで開きます。
サブスクリプション管理ガイドを参照してください。
請求の問題の Webhook に対応する
アプリ内バナーが役立つのは、アプリを開くユーザーだけです。それ以外のユーザーに届けるには、サーバーで RevenueCat の Webhook を使い、メールやプッシュ通知をトリガーします。
BILLING_ISSUE: 支払い失敗が検知された時点で発火します。RevenueCat は問題ごとに 1 回送信します。猶予期間が設定されていれば、回復の期限であるgrace_period_expiration_at_msが含まれます。cancel_reason: BILLING_ERRORを伴うCANCELLATION: これも失敗が検知されたときに発火します。expiration_reason: BILLING_ERRORを伴うEXPIRATION: 猶予期間が回復のないまま終了した場合のみ発火します。ここでようやくアクセスを削除します。RENEWAL: 支払いが回復したときに発火し、billingIssueDetectedAtが解除されます。
// Your webhook endpoint (Express-style)
app.post('/revenuecat/webhook', (req, res) => {
const event = req.body.event;
switch (event.type) {
case 'BILLING_ISSUE':
// Card failed but access continues. Email "update your payment method"
// with the deadline from event.grace_period_expiration_at_ms.
sendFixPaymentEmail(event.app_user_id, event.grace_period_expiration_at_ms);
break;
case 'RENEWAL':
// Payment recovered. Stop the dunning sequence.
clearDunning(event.app_user_id);
break;
}
res.sendStatus(200);
});
EXPIRATION (BILLING_ERROR) は猶予期間が終わるまで遅延され、督促(dunning)シーケンスが効く時間が生まれます。設定がなければ BILLING_ISSUE と同時にすぐ発火し、アクセスは一度に失われます。これこそ、ステップ 3 と 4 で設定した猶予期間が重要な理由です。
サンドボックスでテストする
リリース前にループ全体を検証しましょう。
- App Store Connect で Sandbox(または本番とサンドボックス)の猶予期間を有効にし、サンドボックステスターで動作を試せるようにします。
- Google Play では、Closed testing トラックでライセンステスターを使い、サブスクリプションを猶予期間と保留に入れます。
- 更新の失敗を発生させ、アプリで
proエンタイトルメントがまだisActiveでbillingIssueDetectedAtが設定されていること、そしてバナーが表示されることを確認します。 - サーバーが
grace_period_expiration_at_ms付きのBILLING_ISSUEWebhook を受け取ったことを確認します。 - 支払いを回復させ、バナーが消えて
RENEWALWebhook が届くことを確認します。
まとめ
非自発的チャーンを取り戻す一連の回復フローを構築しました。
- 両ストアの猶予期間が、カードのリトライ中も支払っているユーザーのアクセスを保ちます。
- RevenueCat がその状態をアクティブなエンタイトルメント +
billingIssueDetectedAtとして表し、これをアプリ内で検知します。 - Customer Center(または
managementURL)が、ワンタップで支払いを更新する手段をユーザーに与えます。 - Webhook がアプリ外の督促を動かし、猶予期間がそのタイミングに余裕を持たせます。
これは、実施できる変更の中でもとりわけ効果の高いものの一つです。離れたいとは思っていなかった顧客から、収益を取り戻すのですから。
さらに進む
- CustomerInfo の取得と CustomerInfo 更新リスナー: 検知の構成要素です。
- サブスクリプションの管理: managementURL パターンを詳しく解説します。
- RevenueCat: 請求の問題と猶予期間と Customer Center。