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

10年前の SKPaymentQueue を StoreKit 2 へ — 壁紙アプリ4本の課金コードを Antigravity と移し替えた記録

SKPaymentQueue 依存の課金コードを StoreKit 2 の Transaction.currentEntitlements へ移行した実例です。壁紙アプリ4本での手順、Antigravity エージェントへの任せ方、実機でつまずいた点を記録しています。

storekit2ios25in-app-purchaseantigravity340swift6

2026年5月、iOS の壁紙アプリ4本の年次更新を進めていたとき、Xcode 26 のビルドログに流れる StoreKit の deprecation 警告が、いよいよ無視できない量になっていることに気づきました。課金まわりのコードは2015年頃に書いた SKPaymentQueue ベースのまま、10年近くほぼ無修正で動き続けていたのです。

2014年に個人開発を始めてから、アプリ群を累計5,000万ダウンロードまで少しずつ育ててきましたが、その間ずっと「動いている課金コードには触らない」が私の暗黙のルールでした。課金は事故が許されない領域で、書き換えのリスクとリターンが見合わないと感じていたからです。それでも今回、Firebase Apple SDK の CocoaPods → SPM 移行で詰まった3つのポイント — 4アプリ実例からで書いた SPM 移行と同じタイミングで、StoreKit 2 への移し替えを決めました。ビルドシステムを刷新する機会は数年に一度しか来ないので、課金層も一緒に動かすのが結果的に一番安全だと判断したためです。

この記録が、同じように古い課金コードを抱えている方の判断材料になれば幸いです。

なぜ10年動いたコードを置き換えるのか

判断の決め手は3つありました。

1つ目は「復元」体験の劣化です。旧 API の restoreCompletedTransactions() は Apple ID のパスワード入力を求める場面があり、機種変更したユーザーから「広告除去を買ったのに戻らない」という問い合わせが毎月のように届いていました。StoreKit 2 の Transaction.currentEntitlements は現在有効な権利を直接列挙できるため、そもそも「復元ボタン」という概念がほぼ不要になります。

2つ目はレシート検証です。旧方式ではレシートをサーバーへ送って openssl で検証するか、危険を承知でクライアント検証するかの二択でしたが、StoreKit 2 はトランザクション自体が JWS 署名付きで、検証済みの状態で API から渡されます。個人開発で検証サーバーを保守し続けるコストを考えると、これだけでも移行する価値がありました。

3つ目は async/await との整合です。アプリ本体のコードがすでに Swift Concurrency 前提に書き換わっていく中で、デリゲートベースの SKPaymentTransactionObserver だけが古い世界に取り残され、橋渡しのグルーコードが増え続けていました。

Before / After — 購入処理はここまで短くなる

まず、10年動いていた旧コードの骨格です。

// Before: SKPaymentQueue ベース(2015年頃に書いたもの)
// デリゲート登録・購入・復元・検証がすべて別の場所に散らばる
class IAPManager: NSObject, SKPaymentTransactionObserver {
    func purchase(productID: String) {
        let payment = SKMutablePayment()
        payment.productIdentifier = productID
        SKPaymentQueue.default().add(payment) // 結果はデリゲートで受ける
    }
 
    func paymentQueue(_ queue: SKPaymentQueue,
                      updatedTransactions transactions: [SKPaymentTransaction]) {
        for tx in transactions {
            switch tx.transactionState {
            case .purchased, .restored:
                // ここからさらにレシート検証へ続く…
                SKPaymentQueue.default().finishTransaction(tx)
            default: break
            }
        }
    }
}

StoreKit 2 では、購入と権利確認が次の形に集約されます。

// After: StoreKit 2(iOS 15+ / 壁紙アプリ4本に導入した実コードの骨格)
import StoreKit
 
@MainActor
final class EntitlementStore: ObservableObject {
    @Published private(set) var isAdFree = false
 
    // 起動直後に必ず呼ぶ: 現在有効な権利を列挙して状態を再構築する
    func refresh() async {
        var adFree = false
        for await result in Transaction.currentEntitlements {
            // verified 以外(改ざん疑い)はここで弾かれる
            guard case .verified(let tx) = result else { continue }
            // 返金・ファミリー共有停止済みは revocationDate が入る
            if tx.productID == "com.example.adfree", tx.revocationDate == nil {
                adFree = true
            }
        }
        isAdFree = adFree
        print("entitlements refreshed: isAdFree=\(isAdFree)")
        // 期待される出力(購入済み端末): entitlements refreshed: isAdFree=true
    }
 
    // 購入: 結果が戻り値で返る。デリゲートは不要
    func purchaseAdFree() async throws {
        guard let product = try await Product.products(for: ["com.example.adfree"]).first else { return }
        let result = try await product.purchase()
        if case .success(let verification) = result,
           case .verified(let tx) = verification {
            await tx.finish() // finish を忘れると未完了として残り続ける
            await refresh()
        }
    }
}

デリゲート・レシート検証・復元処理が消え、課金層の実装は4アプリ平均でおよそ3分の1の行数になりました。書き換え自体よりも「どこまで消してよいか」の見極めが、この移行の本体だったと感じています。

Antigravity エージェントにどこまで任せたか

4アプリ分の機械的な書き換えは Antigravity のエージェントに委ねました。進め方はAntigravity × Xcode 26 × iOS 26 — WWDC 2026 に備える iOS 開発者のための実践ガイドで書いた構成と同じで、Planning モードで移行計画を立てさせてから、1アプリ目だけを人間が細部までレビューし、残り3本へ横展開する流れです。

ただし、1箇所だけエージェントに触らせなかった場所があります。広告除去状態の最終判定ロジックです。私のアプリでは買い切りの広告除去と、リワード広告視聴による一時的な広告非表示が併存していて、表示可否は isAdFree || isRewardAdFree の合成判定を必ず経由する設計にしています。エージェントは migration の文脈でこの合成判定を「冗長」と見なして単純化しようとしたことがあり、ここを自動で書き換えられると収益と UX の両方が壊れます。機械的な API 置換は任せる、お金の流れを決める判定は人間が握る、という線引きは最後まで守りました。

実機とサンドボックスでつまずいた3点

横展開の途中、テストで3つの想定外がありました。

  • Transaction.updates の listen 漏れ: 購入フロー外で届くトランザクション(Ask to Buy の承認後など)は Transaction.updates で受けて finish() する必要があります。これを起動直後から listen していないと、未完了トランザクションが再起動のたびに蘇り、購入ダイアログが繰り返し出る状態になりました。
  • オフライン初回起動で権利が空に見える: currentEntitlements はローカルキャッシュから返るのが基本ですが、再インストール直後かつオフラインという条件では空の列挙になり得ます。「空=未購入」と即断して UI を切り替えず、前回判定値を保持した上で次回オンライン時に上書きする実装に変えました。
  • 返金の revocationDate チェック漏れ: エージェントが生成した初版コードは列挙された権利をすべて有効として扱っており、返金済みトランザクションを弾いていませんでした。サンドボックスの返金テストで気づけましたが、レビューなしで通していたらと思うと冷や汗が出ます。

このあたりの「公式ドキュメントを読んだだけでは順番に気づけない罠」は、Antigravity で新 iPhone の解像度対応を乗り越えた話 — iPhone Air / 17 Pro 対応で29箇所ハマった実例のときと同じで、結局は実機での観察がいちばんの教師でした。

次の一歩 — まず「読むだけ」の診断コードから

もし手元に SKPaymentQueue 時代の課金コードが残っているなら、いきなり書き換えるのではなく、Transaction.currentEntitlements を列挙してログに出すだけの診断コードを既存アプリに足してみてください。旧実装を一切壊さずに、StoreKit 2 から見えている権利の世界と、自分のアプリが信じている課金状態のずれを観察できます。私の場合、このずれの一覧がそのまま移行計画になりました。

10年動いたコードを置き換えるのは、率直に言ってまだ少し怖さが残ります。それでも、検証済みトランザクションが言語の型として渡ってくる世界に移ってみると、あの頃レシート検証に費やした時間は何だったのかと思うほど見通しが良くなりました。同じ移行を控えている方の参考になれば幸いです。

シェア

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

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

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

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-05-20
Localizable.xcstrings の8言語翻訳を Antigravity に2週間任せた運用メモ
Localizable.xcstrings に追加した新規キーの8言語翻訳を、Antigravity に2週間下書きさせて運用した記録です。任せた範囲と最終判断に残した範囲、想定外だった挙動、既訳資産との突き合わせ方を率直に書きました。
アプリ開発2026-05-13
AntigravityでGoogle ML Kitを試したら、公式ドキュメントより速く動いた — でも1つだけ詰まった
Google ML KitをAntigravityでiOS/Androidアプリに統合する実践ガイド。テキスト認識・顔検出の実装コードとXcode 15バグ回避策、個人開発での使いどころをまとめています。
アプリ開発2026-05-04
Swift Testing × Antigravity — XCTest の限界を超えて AI 駆動テスト設計へ
Swift Testing フレームワークと Antigravity の組み合わせで iOS テスト品質を根本から変える実践ガイド。@Test・#expect マクロの活用から XCTest 移行戦略、Antigravity によるテスト自動生成まで体系的に解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →