anz blog

Promoting IAP対応で躓いた話

2023-04-28 #Swift

課金自体は先に対応はしていたアプリにあとから Promoting IAP の対応をした時に、何故か正常に動作しないことがあったのでそこらの話。

Promoting IAP を雑に説明しておくと、対応することで App Store 上で課金アイテムが表示されるようになるものです。そしてそれをタップすると直接課金へ誘導できるものです。実際にタップする人がすくなくても、露出が増えるので認知を上げるという目的からも良さそう…ということで今回後追いで対応することになったのです。

問題

今回 Promoting IAP を対応したときは、即座に支払いフェーズへ移行するのではなく、課金アイテムを説明する画面へ遷移させて、そこからユーザーさんに問題がなければ支払いへ進んでもらうという感じで実装していました。

ところが、リリースされたものを確認すると、アプリがバックグラウンドにいる状態で App Store 上の課金情報をタップすると期待している挙動になるものの、
アプリが kill されてる状態であったり、未インストールであった場合に、期待している挙動にならなかったのです。
kill されている状態では、アプリは起動するものの課金アイテム説明画面へは遷移しないですし、未インストールの時も同様で、インストールされてアプリは起動されるもののアイテム説明画面へは遷移しませんでした。

バックグラウンド状態ではちゃんと説明画面へ遷移するので、実装自体が全くできていないわけではなさそうでした。
挙動とか見ているとどうも、Promoting IAP 対応時に実装した SKPaymentTransactionObserver.paymentQueue(_:shouldAddStorePayment:for:) が呼ばれていないような感じです。

結論

ざっくり結論から言うと、 PaymentTransactionObserverの登録タイミングが遅いのが原因 でした。

課金周りを実装するにあたって SKPaymentTransactionObserver を登録するのは必須なステップで、そこは僕もちゃんと実装していたのですが、それのタイミングが遅かったということです。
もちろんドキュメントをそれなりに読んでから課金周りの対応はしているので、AppDelegate の application(_:didFinishLaunchingWithOptions:) 内で登録はしていたのですけれど、それの中での書いてある順番が遅かったという感じです。

具体的にコードを書くと対応前は下記のような感じです。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    ... Analytics とか広告とかその他諸々の初期化系の処理多数 ...

    // トランザクション監視
    SKPaymentQueue.default().add(PaymentsObserver.shared)
}

これが対応後は下記のような感じに変更しました。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    // トランザクション監視
    SKPaymentQueue.default().add(PaymentsObserver.shared)
    
    ... Analytics とか広告とかその他諸々の初期化系の処理多数 ...
}

こうすることで、アプリが kill されている時も、未インストールの時も、ちゃんと説明画面へ遷移するようになりました。
ということで、Observer の登録は application(_:didFinishLaunchingWithOptions:) の中でも可能な限り早いタイミングでやっておくほうが良さそうです。

ちなみに、Promoting IAP のテストは DeepLink を使うことでできます。

itms-services://?action=purchaseIntent&bundleId=com.example.app&productIdentifier=product_name

これを適宜自分のものに変更してやってあげると擬似的に Promoting IAP の挙動を見ることができます。
これで、アプリが kill されている状態でもちゃんと期待してる挙動になっていればおそらく Observer の登録タイミング自体には問題無いはずです。
最初 Promoting IAP の対応をした時に僕もこれを使ってテストしていたのですけれど、最初はアプリが kill されている時は正常に動いていませんでした(問題で上げたのと同じで、アプリは起動するけど説明画面へ遷移しない)。
それが上記で記述したような対応をした後は、期待している挙動になったのでおそらくこれで登録タイミングの問題の有無は確認できるはずです。

いや、正常に動いてなかったのならリリースするなよっていう話でもあるのですが、同じ現象でぐぐったら StackOverflow さんやら Apple Developer Forum やらでおなじ書き込みがあったので、「この DeepLink ではそういうものなのかな…確かにちょっと変則的なものだしちゃんとリリースしたものだと確認できないのかもなー」などと思い込んでしまったのが敗因です… 😇

Lifecycle 周りの話

以上のことから application(_:didFinishLaunchingWithOptions:) が完了する前の早いタイミングで paymentQueue(_:shouldAddStorePayment:for:) が呼ばれることがあるのがわかりました。
これはアプリが kill されているときや未インストールのときの話で、アプリがバックグラウンドに回っている時は paymentQueue(_:shouldAddStorePayment:for:) はいつ頃よばれるのかというと、僕が計測した感じでは applicationWillEnterForeground(_:) より遅く applicationDidBecomeActive(_:) よりは早いタイミングで呼ばれるケースと、applicationDidBecomeActive(_:) より更に後に呼ばれるケースの2パターンがありました。
とくに初めて AppStore から遷移してくるときには applicationDidBecomeActive(_:) より遅いケースで、それ以降は、applicationDidBecomeActive(_:) よりは早いケースという感じでしたね。

とはいえ、ここらが呼ばれるタイミングは確定しているわけではなく、アプリやそのデバイスの状況にも影響されそうなので、 paymentQueue(_:shouldAddStorePayment:for:) はいつ呼ばれてもちゃんと処理できるように実装しておくことが肝要です。特定のライフサイクルのタイミングに依存するように実装してしまうと予期せぬタイミングで呼ばれて意図した挙動にならない可能性がありますから。

参考