ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-06-23上級

StoreKit 2 で「課金したのに使えない」と言われたとき — サブスク権利状態のズレを直す運用メモ

StoreKit 2 のサブスクリプションは実装より運用でつまずきます。currentEntitlements と subscription.status のズレ、Transaction.updates の取りこぼし、未 finish トランザクション、復元と返金の扱いを、本番で効いた整流ロジックとともに記録します。

StoreKit 23サブスクリプション6iOS24Antigravity264Swift8app-dev35

プレミアム記事

ある朝、アプリのサポート窓口に「年額プランを買ったのに、有料機能のロックが外れません」という連絡が届きました。レシートのスクリーンショットを見る限り、購入は確かに成立しています。App Store Connect の売上にも計上されています。それでも、その方の端末ではアプリが「未購入」と判断していました。

StoreKit 2 のサンプルコードはどれも美しく、try await product.purchase() の数行で課金が終わるように見えます。けれど本番でほんとうに難しいのは購入の瞬間ではなく、購入した後に「この人は今、権利を持っているのか」を端末とサーバの両方で食い違いなく答え続けることでした。ここでは、その食い違い——権利状態のドリフト——をどう減らしたかを、実際に踏んだ落とし穴の順に記録します。


「購入」と「権利」を別物として扱う

最初のつまずきは、設計の言葉づかいにありました。purchasedProductIDs のような集合を持ち、購入時にそこへ製品 ID を足す——多くの入門実装がこの形です。けれど購入は一度きりのイベントで、権利(entitlement)は時間とともに変わる状態です。自動更新サブスクは毎月静かに更新され、失効し、返金され、Billing Retry で宙づりになります。購入イベントだけを集めても、今この瞬間の権利は表せません。

そこで、アプリ内部の真実源を「購入履歴」ではなく「現在の権利」に置き直しました。型としても分けています。

// 「今、何を使えるか」だけを表す。購入の履歴ではない
struct Entitlement: Equatable {
    enum State: Equatable {
        case active(until: Date?)        // 有効(until=nil は買い切り)
        case inGracePeriod(until: Date)  // 支払い再試行中。猶予で使わせる
        case expired
    }
    let productID: String
    let state: State
}

この inGracePeriod を最初から型に持たせたのが、後で効きました。返金や決済失敗を「即座に無効」とだけ扱うと、Apple 側がまだ猶予を与えている利用者を、アプリが先回りして締め出してしまうからです。


currentEntitlements を「唯一の真実」にしない

StoreKit 2 で現在の権利を引くと言えば Transaction.currentEntitlements です。私も最初はこれだけを信じていました。けれどサポート事例を追っていくと、currentEntitlementsそのデバイスのローカルキャッシュを反映するに過ぎない場面が見えてきました。別の端末で更新された、ネットワークが不調だった、署名検証で弾いて握りつぶした——理由はさまざまですが、結果として「Apple のサーバは有効と知っているのに、この端末の currentEntitlements には出てこない」状態が起こります。

そこで三つの情報源を、信頼度の順に重ねることにしました。

情報源答えるもの弱点
Transaction.currentEntitlementsこの端末が把握する有効トランザクションローカルキャッシュ依存・取りこぼしあり
Product.SubscriptionInfo.Status更新状態・猶予・失効理由製品が手元に load 済みである前提
App Store Server Notifications V2サーバ側の確定事実(返金・失効・更新)自前のエンドポイントが要る

個人開発でサーバを持ちたくない気持ちは痛いほど分かります。RevenueCat のような外部サービスに in-app purchase の状態管理ごと預ける選択肢もありますし、課金率や継続率の計測まで含めて任せたい場合はお勧めします。それでも私は、依存を増やしたくなかったので最小構成を選びました。「課金したのに使えない」の再発を本当に止められたのは、サーバ側の確定情報を一つ持ってからでした。Apple からの通知を受けて最後の確定状態を記録しておくだけで、端末の言い分とサーバの事実を突き合わせられます。この一手間は、LTV を毀損しないためにも入れておくことを推奨します。


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

この記事の続きを読む

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

この記事で得られること
currentEntitlements・subscription.status・サーバ通知のどれを真実源にするかの判断基準と、起動時に状態を整流する reconcile 実装
Transaction.updates を取りこぼす3つの経路(detached Task の寿命・アプリ未起動中の更新・finish 忘れ)と、それぞれの塞ぎ方
返金・失効・Billing Retry を「無料に戻す」ではなく猶予つきで扱う entitlement 設計と、サンドボックスと本番の挙動差の見分け方
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

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

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

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

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

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

関連記事

アプリ開発2026-04-05
Antigravity × StoreKit 2 サブスクリプション収益最大化の上級戦略 — 解約防止・価格最適化・AI分析まで完全解説
StoreKit 2 のアプリ内サブスクリプションを単なる実装から「収益エンジン」へ昇華させる上級ガイド。App Store Server Notifications v2、解約率AI分析、価格A/Bテスト、Promotional Offersまでを実践コードとともに徹底解説します。
アプリ開発2026-04-01
Antigravity × SwiftData 実践ガイド — Core Data からの移行とデータ永続化の新標準
iOS 17 以降のデータ永続化標準 SwiftData を Antigravity の AI エージェントで効率よく実装する方法を解説。Core Data との違い、基本的な CRUD 操作、既存プロジェクトからの移行手順をコード例付きで丁寧に説明します。
アプリ開発2026-05-26
アプリ内レビュー要求の出し分け条件を Antigravity Editor で 5 アプリ統一した数日の記録
個人開発で運用してきた iOS 壁紙アプリ 5 本の SKStoreReviewController 呼び出し条件を、Antigravity Editor のマルチファイル編集機能でひとつの基準にまとめ直したときの数日間の所感を、コード例とあわせて静かに記録しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →