ガンズターン 公式サイト

楽しいことに、まじめです。 ——ガンズターンアプリ研究所公式サイト

Missing restore mechanism でリジェクトされた件

Pocket

というわけで、こちらの記事の続きをば。
ガンズターンのRyosukeです。

これからはリジェクトされるたびに、こうしてブログの記事にしていこうと思います。

この記事はわたしと同じように「Missing restore mechanism」でリジェクトされて、ほとほと困り果ててGoogle検索したあなたに向けて書いています。

そう。今まさに、ディスプレイの前にいるあなたのために、書いているのです。

1. そもそもどこを直せばいいのか

ブログをはじめて一週間ではじめて気づいたのですが、わたし、関係ない文章を書き始めると何時間でも書き続けていられるようです。
結果として、無駄な文章ばかりの記事になってしまうことが多いようなので、今日は簡潔にまとめたいと思います。

結論から述べますと、この理由でリジェクトされたアプリに対してAppleが求めている修正内容は、「購入情報を復帰するための仕組みを実装してね」ということなんですね。

2. 「りずもぐ!」の場合

わたしが初めて作成したアプリ「りずもぐ!」では、楽曲パックをユーザーが購入できる仕組みがあります。(といっても、まだ1パックしか販売できてませんが)

この楽曲パックの購入情報は、In-App-Purchaseで取り扱えるアイテムのうち「Non-Consumable」というタイプのものです。
そのため、例え「りずもぐ!」をユーザーがアンインストールしたとしても、Apple側で購入情報を恒久的に保持してくれます。

例えば同一ユーザーが「りずもぐ!」を一度アンインストールして、気まぐれに再度インストールした状況を考えてみましょう。

当然、アンインストールした時点で、端末からは「りずもぐ!」に関する購入情報はすべて消えてしまいます。
けれど、Appleのサーバが保持している情報からは消えるわけではありません。

結果、再度同じユーザーが「りずもぐ!」をインストールし、一度購入した楽曲パックを再度「購入」する操作をしたとしましょう。

果たして、そのユーザーにはまた、請求がいくのでしょうか?

結果として、同じ「楽曲パック」に対する二度目の「購入」操作に対しては、課金されることはありません。
そのため、ユーザーに再度請求が行くということは起こらないわけです。

だから、実際にアプリに実装する仕組みは「購入」処理だけでも問題は起こらない——

そう考えていた時期がわたしにもありました。

けれど、考えてみてください。
AppleのIn-App-Purchaseの仕様を知っている開発者からしたら当たり前のことであっても、世間一般の、普通にiPhoneを使っているだけの人たちからして見れば、一度「購入」したはずのアイテムをまた「購入」したら、追加で課金されるんじゃないか、と心配になるのが当たり前の反応ではないでしょうか?

はっきり言ってわたし、Appleさんに「Missing restore mechanism」でリジェクトされるまで、こういった考え方には及びもしなかったわけですが……

言われて見れば、確かにエンドユーザーにとっては不親切な仕様以外のなにものでもなかったわけです。

3. じゃあ、どこをどう直す?

というわけで「りずもぐ!」の最新版では、以下のようにして「復元」する処理を実装しました。

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {

            /* 中略(実際にはここに通常の購入時の処理等が書かれています)*/

            // 購入履歴復元
            case SKPaymentTransactionStateRestored: {
                // ↓ここで呼び出す任意のメソッドの中で、
                // アイテム復元完了時の処理を書く
                [self completeRestoreProcess:transaction];
                [queue finishTransaction:transaction];
                break;
            }
        }
    }
}

//1つ1つのアイテムが復元された時に実行される任意のメソッド
- (void) completeRestoreProcess:transaction {
    // 実際にはもうちょっと複雑なことしてますが、簡略化してます。

    // 復元に成功したproductIdを取得。
    NSString *productId = transaction.payment.productIdentifier;

    // 取得したproductIdを一時的にバッファ(NSMutableArrayなど)に保存。
    [self.restoredItems addObject:productId];

    // この時点では、まだ復元情報のアプリ内への反映処理を行わない。
    // バッファに蓄えた情報は全transactionの復元が完了してから参照する。
}

//復元が全て成功した時に呼び出されるデリゲートメソッド
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{
    // このメソッドが呼び出されない限り、すべてのtransactionは終了していない。
    // 従って、実際の復元処理はここで行うこととした。

    for(NSString *productId in self.restoredItems) {
        /* このループ内で、個別のproductIdに応じた復元情報のアプリ内への反映処理を行う。 */
    }

}

//復元が1つでも失敗した時に呼び出されるデリゲートメソッド
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error{
    /* このメソッド内では、復元が失敗した時に実施される処理を行う。
       例えば、エラーメッセージの表示など */
}

かなり簡略化してますが、骨子は上記のとおりです。

注意点としては以下の2点ぐらいですかね。

  1. プロトコル「SKPaymentTransactionObserver」に準拠したクラス内で、上記メソッドを実装する。
  2. メソッド「paymentQueueRestoreCompletedTransactionsFinished」が呼ばれない限り、復元途中のtransactionが存在している可能性がある。

とくに「2.」については、最後まで処理されなかったtransactionの情報などは次回のアプリ起動時に取得されたりして、混乱のもとになるので注意が必要です。

実際、わたしも「??」が飛び交う状況に何度か追いやられました。

4. こうして実装した復元処理で、無事に審査通過しました!

というわけで「りずもぐ!」における購入情報復元処理の実装について、簡単ながらソースコードも交えてご紹介してみました!

この記事が、いつかどなたかのお役に立てるなら幸いです。

明日はまたちょっと砕けた記事に戻る予定です。
ガンズターンのRyosukeでした! m(_ _)m

Pocket

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

トラックバックURL: http://gunsturn.com/2015/05/10/reject_missing_restore_mechanism/trackback/