2020年10月18日日曜日

iOSサブスクリプション実装後に心臓に悪い日々を過ごさなくて済むために読んでおいた方が良い記事

 先日、本業でAppStoreサブスクリプションに対応したサービスをリリースしたのですが、クライアント側の実装に大きな問題があり、現在その対策版の審査待ちという状況です。初めて特急審査出してみました。既にサービス開始してしまっていることから、それだけでもかなり心臓に悪く、眠れぬ日々が続いております。

今後ほかの方が同じ轍を踏まなくて済むように、一体どのような問題が起こり、どのような対処をしたのか可能な限りなるべくわかり易く書き記しておこうと思います。


(AppStoreサブスクリプションの概要)

サブスクリプションを定期購読すると「レシート」と呼ばれるものが発行されます。アプリでこのレシート取得して、自前のサーバへ送信&検証することで、ユーザが購読者か否かを判定する...というのが、基本的なサブスクリプション実装方法です。(詳細はコチラを参照)


(今回発生した問題)

定期購読したユーザがアプリを再起動すると、定期購読中と判定されない不具合が発生しました。

つまり、買ったのに「買ってない」と判定されてしまう致命的なバグです。


(問題の原因)

アプリでレシートを取得するには、Bundle::appStoreReceiptURLというAPIを用いますが、どうもこのAPIがnilを返しているらしいことがサーバのログから判明しました。

不思議なことに暫く時間を置いてからリトライすれば、正しい値が返されます。

これと類似する現象が以下のStack Overflowで公開されていました:

https://stackoverflow.com/questions/20027322/appstorereceipturl-on-mainbundle-always-returns-nil

上記によると、

SKReceiptRefreshRequest *refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}];
refreshReceiptRequest.delegate = self;
[refreshReceiptRequest start];

という形でレシートをリフレッシュする必要があるとのことでした。

ただし、上記実装ではローカル変数(弱参照)でSKReceiptRefreshRequestインスタンスを保持しているので問題があります。

以下の記事にもありますが、SKReceiptRefreshRequestインスタンスは強参照(strong@propertyなど)で保持しなければなりません。

https://asmz.hatenablog.jp/entry/in-app-purchase-not-working-in-tvos

そこで、アプリ起動時(ログイン時)にSKReceiptRefreshRequestインスタンスをリクエスト中は強参照で保持しつつ、リクエストを出す形の修正をしてApple審査に提出しましたが、リジェクトされました。


(審査リジェクト原因)

リジェクト理由は、アプリ起動後にタップ(ログイン)したところハングアップした(ガイドライン2.1違反)とのことです。

以下のStack Overflowに書かれていますが、AppStoreのsandbox環境でSKReceiptRefreshRequest::startを発行すると、クレデンシャルの入力が要求され、それをキャンセルすると二度と応答が返らない(アプリを一度削除してから再インストールする必要がある)とのことです。

https://stackoverflow.com/questions/30114489/skreceiptrefreshrequest-not-working-the-second-time-it-is-called-after-a-cancel

上記のAnswerに、

The only solution is to present an alert, telling the user to remove and download your app again from the store and to not cancel the credential box when the app asks for the apple ID/password. 

翻訳: 唯一の解決策は、ユーザーにアラートを提示し、アプリを削除して再度ダウンロードするように指示することと、アプリがアップルID/パスワードを要求したときにクレデンシャルボックスをキャンセルしないように指示することです。

という、「そんなまさか!?」と思うようなことが書かれていますが、考えうる限りどうもそれが事実のようです。という訳で、Apple審査を通すため、SKReceiptRefreshRequest::startの裏でNSTimerをスケジュールして一定時間(60秒)応答が無かった場合は、「アプリを削除して再度ダウンロードするように指示することと、アプリがアップルID/パスワードを要求したときにクレデンシャルボックスをキャンセルしないように指示」する旨のアラートを表示するという、〇〇(自粛)のような修正を入れて審査再提出しました。


(何故テストしきれないのか)

根本的な原因は、sandbox版AppStoreと本番AppStoreの挙動に違いが多すぎることにあります。

本番AppStoreでテストするには、一度Apple審査を通す必要があります。

Apple審査を通せば「プロモコード」を発行することで、本番AppStoreの挙動をテストできるのでは?と思われるかもしれません(私もそう思っていた時期がありました)が、リリースしていないアプリの場合、プロモコードを発行してもアプリをダウンロードできないので、「ローンチ一発目はぶっつけ本番で一般ユーザと一緒にテストするしかない」という、非常にロックンロールな仕様になっております。

だからバグっていても仕方がない・・・とは言いませんが、テストせずにバグを全て取り除くのは困難です。

実は、今回の対策で本当に問題対策できているかという確証がまだ無いです(本番環境でテストできていないので当然ですよね)。なので、Apple審査(現在審査待ち)に合格したら、今度こそはプロモコードを使って事前テストしたいところです。できなければ、またロックンロールするしかないですね。(審査後にテストできてまた問題が見つかったら、また審査待ちで数日を要し、その数日間がまたハラハラドキドキになってしまいますが、「治ったよ」と宣言して治ってなかったというケースと比べればまだ安心・・・なのだろうか。連日の徹夜続きで既に思考が正常ではないかも)

心臓に悪い日々はまだまだ続きそうだ。

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。

合理的ではないものを作りたい

ここ最近、実機版の東方VGSの開発が忙しくて、東方VGSの曲追加が滞っています。 東方VGS(実機版)のデザインを作りながら検討中。基本レトロUIベースですがシークバーはモダンに倣おうかな…とか pic.twitter.com/YOYprlDsYD — SUZUKI PLAN (...