/* */ /* */

2012年11月26日月曜日

【iPhone】課金処理が勝手に走る状態の防止【StoreKit】

※この記事の内容はどう考えても推奨されません。その場しのぎです。
 実装しちゃって課金処理で不利益出ても何も出来ません。

※iOS7、iOS8で再度組み直してみたのですが、今コレ使えないみたいです。
 ダイアログ出る前にSKPaymentTransactionStatePurchasingが
 飛んできませんでした。

何やらiPhoneの課金処理に関する掲示板などを見ていると、
「アプリを起動すると何故か課金処理が走ってしまう」
というのを時々見かけます。

取りあえずその場しのぎの策に行き着いたので書き残します。

iPhoneアプリを起動して、特に操作をしていないのに、
課金のダイアログが出て、IDとパスワードを求められる事があります。
これは、前回何かしらのアプリを起動して、課金処理に入り、
課金処理が正常に終了しないままアプリが落ちるとなります。
次回起動した後、特定のタイミング(StoreKitさんが要らん親切で通知してくれます。)でダイアログが呼び出されます。

本当に厄介!!

この問題に突き当たるという事は、公式のプログラミングガイド通りに、
アプリ起動直後にオブザーバー(何か課金処理を管理してるっぽい奴)の登録をしているのだと思われます。
そすると、最初に登録したオブザーバーさんが律儀に途中で終わっちゃってるトランザクションを拾って来てくれるみたいです。(しかもストアキットさんの任意のタイミングで・・・)

なので。。。

これ破棄すればオーケー

「ちゃん再処理しろよ」という白い林檎からのプレッシャーを感じますが取り合えず無視
 だっていきなりダイアログ出てくるとか不審過ぎるし、タイミング取得できる様な仕組みも見当たらないし・・・


本題です。
ここではNonCondensableな課金を例として挙げております。


AppDelegate.h

@interface AppDelegate : UIResponder 


AppDelegate.m
//未完トランザクション用

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{

    for (SKPaymentTransaction *transaction in transactions) {

        switch (transaction.transactionState) {

    

            case SKPaymentTransactionStatePurchasing: {// 購入中

                [queue finishTransaction:transaction];

                //アイテムID取得したいならこの部分

                break;

            }

            case SKPaymentTransactionStatePurchased: {// 購入成功

                [queue finishTransaction:transaction];

                break;

            }

            case SKPaymentTransactionStateFailed: { //購入失敗/中断

                [queue finishTransaction:transaction];

                break;

            }

            case SKPaymentTransactionStateRestored: {// 購入履歴復元

                [queue finishTransaction:transaction];

                break;

            }

        }

    }

}


//オブザーバーの削除(NSNotificationCenterで呼ばれる)


-(void)removeTransaction {

    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

}

//オブザーバーの登録(NSNotificationCenterで呼ばれる)

-(void)addTransaction {

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

}


//なんか、スプラッシュ画面で呼ばれてる部分


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    〜〜〜〜〜〜〜

    //何か色んな処理 

    〜〜〜〜〜〜〜


    //オブザーバーの登録

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    

    // 通知センターに登録

    //オブザーバーの破棄をするメソッド

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeTransaction) name:@"remove" object:nil];

    //オブザーバーの登録をするメソッド

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(addTransaction) name:@"addtran" object:nil];

}
という具合に、アプリ起動時に未完トランザクション用のオブザーバーの登録を済ませてしまいます。
未完トランザクションがあった場合、-(void)paymentQueueホニャララが呼ばれますので取りあえずfinishTransactionを呼んじゃいます。
ダイアログが表示されるのは、1回目に
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
に入った後(1回目は購入中に入ります)なので、そこで終了させてしまえば
ダイアログは出ないみたいです。
「任意のタイミングで処理したい」ならば、「//購入中」の部分で
アイテムIDを適当なArrayにでも突っ込んで、
お好きなタイミングで課金処理を走らせると良いかと思います。
取りあえず破棄完了!!
でもこのままだと、未完じゃない新規のトランザクションまで破棄されちゃいます。
なので、別に実際の課金処理を用意してやります。
MyStoreObserver.h
#import 
@interface MyStoreObserver : NSObject
@end
MyStoreObserver.m
@implementation MyStoreObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    NSLog(@"paymentQueue:updatedTransactions");
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchasing:
            {
                // 購入処理中
                break;
            }
            case SKPaymentTransactionStatePurchased:
            {
                [queue finishTransaction:transaction];
                // 購入処理成功時の処理
                break;
            }
            case SKPaymentTransactionStateFailed:
            {
                [queue finishTransaction:transaction];
                // 購入処理エラー。ユーザが購入処理をキャンセルした場合もここにくる
                // エラーが発生したことをユーザに知らせる
                // [self failedTransaction:transaction];
                if (transaction.error.code != SKErrorPaymentCancelled){
                    //エラーの場合
                } else {
                    //ユーザーがキャンセルした場合
                }
                break;
            }
            case SKPaymentTransactionStateRestored:
            {
                [queue finishTransaction:transaction];
                // リストア処理
                [self restoreTransaction:transaction];
                break;
            }
        }    
    }
}
// 全てのリストア処理が終了(その後、「購入処理の終了」が呼ばれる)
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue 
{
     //何かの処理   
}
// リストアの失敗(cancel時も呼ばれる)(その後、「購入処理の終了」は呼ばれない)
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
    //何かの処理
}
// 購入処理の終了
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions 
{    
    //南下の処理
}
@end

んでもって、後は実際課金走らせる部分で
○○○.m
//上の方で宣言しておく
MyStoreObserver *mySKObserver;
~~~~~
~~~~~
//ここでダミー破棄の通知
NSNotification *n = [NSNotification notificationWithName:@"remove" object:self];
[[NSNotificationCenter defaultCenter] postNotification:n];
//重複すると怖いので一応破棄。多分無くても大丈夫
[[SKPaymentQueue defaultQueue] removeTransactionObserver:mySKObserver];
//実際に走らせるオブザーバーを登録
[[SKPaymentQueue defaultQueue] addTransactionObserver:mySKObserver];
~~~~~~~~~~
~~~~~~~~~~
-(void)viewWillDisappear:(BOOL)animated{
    //使い終わったオブザーバーをここで削除
    [[SKPaymentQueue defaultQueue]removeTransactionObserver:mySKObserver];
    //ノーティフィケーションも削除
    [[NSNotificationCenter defaultCenter]removeObserver:self];
    
    //一応もう一回ダミーを立てるべきだろうか・・・
    //NSNotification *n = [NSNotification notificationWithName:@"remove" object:self];
}

という感じで、ダミーのオブザーバーと実際に課金走らせるオブザーバーを交換して上げる。
取りあえずはこれで動いてる感じ!!
参考サイト様一覧
【なんてこったいブログ】
http://nantekottai.com/2011/10/28/storekit/
【アルデンテ】
http://d.hatena.ne.jp/kozy_twt/20110401/1301680959
In-App Purchaseプログラミングガイド
https://developer.apple.com/jp/devcenter/ios/library/documentation/StoreKitGuide.pdf



0 件のコメント:

コメントを投稿