◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/アプリ開発
◇ アプリ開発/2026-10-15上級

購読が切れても広告が戻りませんでした。「いま ad-free でなければ false」と書いていた、判定の向きを直した記録

購読が失効しても広告非表示のままになる不具合の原因は、判定の向きの逆転でした。二系統の購入確認を待って合成し、失敗した回は前回を保つ部品を、手元で動かして確かめた記録です。

広告非表示サブスクリプション66エンタイトルメント3状態管理12運用実録

✦ プレミアム記事

判定の流れを一行ずつ紙に書き出していた夜のことです。個人開発で運用している壁紙アプリには、購入すると広告が消える仕組みがあります。その「広告が消えている状態」が、購読の期限が切れたあとに元へ戻るのかを、私自身はそれまで一度も紙の上で辿ったことがありませんでした。

辿ってみて、手が止まりました。広告を戻す側の分岐が、どこにも書かれていなかったのです。

起きていたのは、購読が失効したのに広告が戻らない、という状況でした。購入した人に広告が出る事故は、気づかれやすく、すぐ問い合わせにもなります。反対向きの事故——払っていない状態で広告が出ないまま——は、誰も困らないので、誰も報告してくれません。静かに続きます。Android 版で見つかり、iOS 版へ同じ判定を移す前の点検項目の最初に書き留めたものです。

最初にお伝えしたいのは、直したのが課金 SDK の使い方ではなく、判定を組み立てる「向き」と「待ち方」だった、という点です。以下は、その考え方を手元で動く形に組み直し、実際に走らせて確かめた書き残しです。

向きが逆だった、という一文で言える誤り

言葉にすると、壊れていた判定は「いまの状態が ad-free でなければ false にする」という一文でした。本来は「購入が確認できなければ false にする」と書くべきところです。主語が、確認した事実ではなく、フラグ自身の現在の値になっていました。

元のコードをそのまま載せることは控えます。ここでは考え方だけを、TypeScript で組み直しました。壊れていた向きの再現です。

// adfree.ts — 広告非表示(ad-free)の判定を、二系統の問い合わせから組み立てる部品
export type Source = "subscription" | "oneTime"; // 購読 / 買い切り
 
export type QueryResult =
  | { ok: true; active: boolean }
  | { ok: false; reason: string };
 
export type Results = Record<Source, QueryResult>;
 
// 壊れていた向きの再現(考え方の再現であり、実際のコードの引用ではありません)
export function applyBuggy(prev: boolean, r: Results): boolean {
  const purchased =
    (r.subscription.ok && r.subscription.active) ||
    (r.oneTime.ok && r.oneTime.active);
  if (purchased) return true;
  if (!prev) return false; // 「いま ad-free でなければ false」: 向きが逆
  return prev;             // 期限が切れても、前回の true が居座る
}

このコードは、購入が確認できたときに true にするところまでは正しく動きます。だから、購入した直後の確認では何も気づけません。壊れるのは、購読が切れたあとの一回だけです。購入の確認が「なし」を返しても、prev が true のままなら true を返し続けます。

この種の不具合は、動いている側のテストでは見つかりません。「購入したら広告が消える」を何度確かめても、緑のままだからです。消える方向の確認と、戻る方向の確認は、別々に書きます。 この線引きを、私は点検表の一行目に置くことにしました。

二系統を待ってから、確認できた事実だけで決める

購入の問い合わせは、一つではありませんでした。購読と買い切りのように、別々に聞く必要のある系統が二つあります。一方が「なし」と返した時点で false にすると、もう一方がこれから「あり」と返す場合に、広告が一瞬だけ出てしまいます。

そこで、判定を次の三つの規則に直しました。

二系統の確認結果決める値理由
一つでも「有効」と確認できたtrue片方が失敗していても、有効の証拠は一つで足ります
両方が「無効」と確認できたfalsefalse にしてよいのは、全系統から無効を聞けたときだけです
どれかが失敗し、有効の証拠もない前回の値のまま決められない回に、答えを作らないためです

これをそのまま関数にします。

// 直した形: 確認できた事実だけで決め、決められないときは前回を保つ
export function composeAdFree(prev: boolean, r: Results): boolean {
  const list = [r.subscription, r.oneTime];
  // 一つでも「有効」と確認できたら、他の系統が失敗していても true
  if (list.some((x) => x.ok && x.active)) return true;
  // 全系統が「無効」と確認できたときだけ false
  if (list.every((x) => x.ok && !x.active)) return false;
  // どれかが失敗し、有効の証拠もない: 決められないので前回を保つ
  return prev;
}

なぜこう書くのか。ポイントは、QueryResult に「失敗」という第三の状態を持たせたところです。active: false は「聞いたら無効だった」、ok: false は「聞けなかった」。この二つを同じ false にまとめてしまうと、通信が途切れただけで広告が戻る、あるいは戻らないままになります。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦自分のアプリの広告非表示フラグが、購読の失効で本当に false へ戻るかを、二系統の問い合わせの組み合わせ表で点検できるようになります
✦二つの問い合わせが揃うまで答えを出さず、失敗した回は前回の状態を保つ判定部品を、課金 SDK から切り離して手元で自動テストできるようになります
✦有料ユーザーに一瞬広告が出る事故と、失効後も広告が戻らない事故という、向きの逆な二つの不具合を、出荷前に先回りして防げるようになります
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

◇ アプリ開発2026-10-15
adjustsFontSizeToFit で特定の1行だけ極小になるとき、疑うのは文字列ではなく枠の高さです
一覧の10行目だけ文字が4ptまで縮む。データも文字列も正常なのに起きる iOS の症状を、Yoga の丸めと縮小判定のソースで確かめ、Node で再現率を数えた記録です。
◇ アプリ開発2026-10-15
EAS Build だけ「No matching variant」で落ちるとき、まず疑う手元の android/build
手元の Gradle は通るのに、EAS Build の Android だけが「No matching variant of project」で止まる。GitHub の報告を読み比べ、Windows で生成した autolinking のキャッシュが Linux のビルド機へ渡っていた経路と、ビルドを投げる前に確かめる診断スクリプトをまとめました。
◇ アプリ開発2026-10-14
App Review で「Guideline 2.1・白い画面」と言われたとき、Expo のリリースビルドで疑う3か所
TestFlight では開くのに、審査では「起動後に白い画面のまま」と差し戻される。Expo + EAS のリリースビルドで、環境変数の environment、secret の可視性、審査員の環境差の3点を順に切り分ける手順を、確認コマンドと診断コードつきでまとめました。