24/7 twenty-four seven

iOS/OS X application programing topics.

iOS 8でIn-App Purchaseの状態に追加されるSKPaymentTransactionStateDeferredの影響を考える

In-App Purchaseでプロダクトの購入を扱うときにはStoreKitのSKPaymentTransactionStateを使います。
例えばPurchasedなら購入完了なのでプロダクトのダウンロードを始める、Failedなら失敗なのでアラートを出すなどとします。

iOS 8からはその状態に新しくSKPaymentTransactionStateDeferredが追加されます。


2014/8/6時点ではまだドキュメントに解説はありません。
API diffとヘッダには記載されています。
WWDCのセッション218, 303ではそれなりに詳しく解説されていますので参考になると思います。


Deferredという状態は「Ask to Buy」というiOS 8のApp Storeの新機能のために導入されました。
「Ask to Buy」はiOS 8で搭載される「ファミリー共有 (Family Sharing)」の1機能で、子どもがApp Storeから購入する際に親の許可を必要とすることができるというペアレンタルコントロールの強化です。

20140806155004 20140806155004

Apple - iOS 8 - ファミリー共有


「Ask to Buy」が有効になっているアカウントで購入しようとしたとき、購入をいったん保留して許可を求める通知が親のアカウントに届きます。
このとき、購入は「完了(Purchased)」でも「失敗(Failed)」でもなく「Deferred」になります。


いずれDeferredは「完了(Purchased)」か「失敗(Failed)」のどちらかになりますが、それがいつになるかは親のアカウントがいつ判断をするかによるので、場合によっては何日かかかることも考えられます。
そのため、セッション303によると、この状態のときは通常通り(購入前の状態で)アプリケーションが使えるようにしなければならないとのことです。


さて、上記を踏まえて、既存のアプリケーションについてどのような対応が必要になるか考えてみます。


通常In-App Purchaseをハンドリングするオーソドックスな実装は下記のようになっていると思います。

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                break;
            case SKPaymentTransactionStatePurchased:
                [self sendReceipt:transaction];
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                break;
            default:
                break;
        }
    }
}


そして、購入を開始してから終了(完了または失敗)までをモーダルに処理しているアプリケーションも多いと思います。
(そのほうがわかりやすいことも多いので、購入開始〜完了までをモーダルにするのは別に悪いUIでは無いと思います)


ただ、その場合は上記の実装をそのままにしておくと、iOS 8でSKPaymentTransactionStateDeferredが返ってきたときに完了にも失敗にもならないので、購入中のモーダルな状態のままになってしまうことになります。

そこで、上記のswitch文にSKPaymentTransactionStateDeferredの分岐を追加します。
おそらくいったん失敗(Failed)と同じ処理をするのが簡単じゃないかと思います。
ただし、おそらく失敗のときとは異なり、トランザクション自体は終了させない(+ [SKPaymentQueue finishTransaction:]を呼ばない)のではないかと思います。
(このあたり、Sandbox環境で試すことはまだできないようですし、ドキュメントも無いので実際にどうなのかは不明です)


もともと、購入中でもモーダルにしてなくて自由に通常の操作ができる、という作りのアプリケーションの場合は特に気にせずゆっくり対応しても大丈夫そうかなと思います。
Deffered以外の既存の購入フローには影響はなさそうなので。

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                break;
            case SKPaymentTransactionStatePurchased:
                [self sendReceipt:transaction];
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                break;
            case SKPaymentTransactionStateDeferred: 
                // Deferredの状態に対応する処理を追加する
                // おそらく「失敗(Failed)」と同様にするのが簡単
                // ただし、トランザクションは終了させない(と思う)
                break;
            default:
                break;
        }
    }
}


「ファミリー共有 (Family Sharing)」および「Ask to Buy」の機能がいつ、既存のアプリケーションに対して適用されるのかとかもよくわかりませんが、In-App Purchaseを利用している場合は何らかの対応を検討する必要がありそうだなと思います。