2013年5月31日金曜日

iphoneの課金処理を見通しよくする

objective-cには無名クラスが無い(たぶん)。
なのにだいたいインタフェースがオブザーバー。
その都度処理かえるのがめんどい。

ということでBlocksインタフェースをつけちゃいます。
今日はStoreKit。

目標

課金処理の見通しよくする

使う側をこんな感じに

課金処理で結局何がしたいかというと、
・課金をリクエストして成功したか失敗したか。
・リストアをリクエストして成功したか失敗したか

controller.m

// 購入処理
 SLSK* slsk = [SLSK getInstance];
 slsk.onFail = ^(NSError* error){
    NSLog(@"%@", error.debugDescription);
 };
 slsk.onPurchaseSuccess = ^(NSString* productIdentifier){
    NSLog(@"%@ 購入完了", productIdentifier);
 };
 [slsk puchase:@"com.subakolab.tyarintyariiiiiiin" /*productIdentifier*/];
// リストア
 SLSK* slsk = [SLSK getInstance];
 slsk.onFail = ^(NSError* error){
    NSLog(@"%@", error.debugDescription);
 };
 // リストア対象のアドオンの数だけ呼ばれる
 slsk.onRestoreSuccess = ^(NSString* productIdentifier){
    NSLog(@"リストアアドオン:%@", productIdentifier);
 };
 // リストアが全部終わったら呼ばれる
 slsk.onRestoreComplete = ^(){
    NSLog(@"リストア完了!");
 };

ということでインタフェースはこんな感じ

@property (strong) SLSKOnPurchaseSuccess_t onPurchaseSuccess;
@property (strong) SLSKOnFail_t onFail;
@property (strong) SLSKOnCancel_t onCancel;
@property (strong) SLSKOnRestoreSuccess_t onRestoreSuccess;
@property (strong) SLSKOnRestoreComplete_t onRestoreComplete;
@property (strong) SLSKOnComplete_t onComplete;

// singleton
+ (SLSK*)getInstance;

// 処理
-(void) purchase:(NSString*)productIdentifier;
-(void) restore;
// 確認
-(BOOL) checkSk;
-(BOOL) isConnectiong;

以下、コード

SLSK.h

#import 
#import 

typedef enum SLSK_STATE{
    SLSK_PRODUCT_REQUEST,
    SLSK_PURCHASE_REQUEST,
    SLSK_RESTORE_REQUEST,
    SLSK_WAIT
} SLSKState;

typedef enum SLSK_REQUEST_TYPE{
    SLSK_REQUEST_PRODUCT,
    SLSK_REQUEST_PURCHASE,
    SLSK_REQUEST_RESTORE,
    SLSK_REQUEST_NONE
} SLSKRequestType;

typedef void (^SLSKOnPurchaseSuccess_t)(NSString* productIdentifier);
typedef void (^SLSKOnFail_t)(NSError* error);
typedef void (^SLSKOnCancel_t)();
typedef void (^SLSKOnRestoreSuccess_t)(NSString* productIdentifier);
typedef void (^SLSKOnRestoreComplete_t)();
typedef void (^SLSKOnComplete_t)();

@interface SLSK : NSObject<
SKProductsRequestDelegate
, SKPaymentTransactionObserver
>{
    // callbacks
    SLSKOnPurchaseSuccess_t onPurchaseSuccess;
    SLSKOnFail_t onFail;
    SLSKOnCancel_t onCancel;
    SLSKOnRestoreSuccess_t onRestoreSuccess;
    SLSKOnComplete_t onComplete;
    SLSKOnRestoreComplete_t onRestoreComplete;
    
    // params
    SLSKState state;
    SLSKRequestType currentRequestType;
    NSMutableDictionary* productList;
}

@property (strong) SLSKOnPurchaseSuccess_t onPurchaseSuccess;
@property (strong) SLSKOnFail_t onFail;
@property (strong) SLSKOnCancel_t onCancel;
@property (strong) SLSKOnRestoreSuccess_t onRestoreSuccess;
@property (strong) SLSKOnRestoreComplete_t onRestoreComplete;
@property (strong) SLSKOnComplete_t onComplete;

// singleton
+ (SLSK*)getInstance;

// 処理
-(void) purchase:(NSString*)productIdentifier;
-(void) restore;
// 確認
-(BOOL) checkSk;
-(BOOL) isConnectiong;

@end

SLSK.m

#import "SLSK.h"

@implementation SLSK

@synthesize onPurchaseSuccess;
@synthesize onFail;
@synthesize onComplete;
@synthesize onCancel;
@synthesize onRestoreSuccess;
@synthesize onRestoreComplete;

// ▼singleton ============================
// http://programming-ios.com/objective-c-singleton/ より

+ (SLSK*)getInstance {
    static SLSK* sharedSingleton;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedSingleton = [[SLSK alloc]
                           initInstance];
    });
    return sharedSingleton;
}

- (id)initInstance {
    self = [super init];
    if (self) {
        [self initialize];
    }
    return self;
}

- (id)init {
    [self doesNotRecognizeSelector:_cmd];
    return nil;
}

// ▲ singleton ===============================

-(void) initialize{
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    productList = [[NSMutableDictionary alloc] init];
    state = SLSK_WAIT;
    currentRequestType = SLSK_REQUEST_NONE;
}

// ▼ SKProductsRequestDelegate =================

// TODO 複数対応
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    NSArray* products = response.products;
    
    state = SLSK_WAIT;
    
    // productIdentifierが見つからない
    if([products count] == 0){
        currentRequestType = SLSK_REQUEST_NONE;
        NSError* error = [NSError errorWithDomain:@"productIdentifierが無いよerrorDomain"
                                             code:1
                                         userInfo:@{@"debugDescription":@"たぶん無効なproductIdentifier"}];
        if(onFail)
            onFail(error);
        if(onComplete)
            onComplete();
        return;
    }
    
    for(SKProduct* product in products){
        if([productList objectForKey:product.productIdentifier] != nil)
            continue;
        [productList setObject:product forKey:product.productIdentifier];
    }
    
    SKProduct* product = [products objectAtIndex:0];
    if(currentRequestType == SLSK_REQUEST_PURCHASE){
        [self purchaseImple:product];
    }
}

// ▼ SKPaymentTransactionObserver ==============

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0){
    for(SKPaymentTransaction* transaction in transactions){
        switch(transaction.transactionState){
            case SKPaymentTransactionStatePurchasing:
                NSLog(@"SKPaymentTransactionStatePurchasing");
                break;
            case SKPaymentTransactionStatePurchased:
                NSLog(@"SKPaymentTransactionStatePurchased");
                if(onPurchaseSuccess)
                    onPurchaseSuccess(transaction.payment.productIdentifier);
                [self finishRequest:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                NSLog(@"SKPaymentTransactionStateFailed");
                if(onFail)
                    onFail(transaction.error);
                [self finishRequest:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                if(onRestoreSuccess)
                    onRestoreSuccess(transaction.payment.productIdentifier);
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            default:
                break;
        }
    }
}

- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0){
    NSLog(@"removedTransactions");
}

- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0){
    NSLog(@"restoreCompletedTransactionsFailedWithError");
    if(onFail)
        onFail(error);
    if(onComplete)
        onComplete();
    state = SLSK_WAIT;
}

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0){
    NSLog(@"paymentQueueRestoreCompletedTransactionsFinished");
    if(onRestoreComplete)
        onRestoreComplete();
    if(onComplete)
        onComplete();
    state = SLSK_WAIT;
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_6_0){
    NSLog(@"updatedDownloads");
}

// ▼ private ===============================

-(void) finishRequest:(SKPaymentTransaction*) transaction{
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    if(onComplete)
        onComplete();
    state = SLSK_WAIT;
    currentRequestType = SLSK_REQUEST_NONE;
}

-(void) purchaseImple:(SKProduct*)product{
    state = SLSK_PURCHASE_REQUEST;
    SKPayment* payment = [SKPayment paymentWithProduct:product];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

-(void) restoreImple{
    state = SLSK_RESTORE_REQUEST;
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

-(void) productRequest:(NSString*)productIdentifier{
    SKProductsRequest* productRequest = [[SKProductsRequest alloc]
                                         initWithProductIdentifiers:[[NSSet alloc] initWithObjects:productIdentifier, nil]];
    productRequest.delegate = self;
    [productRequest start];
}

-(void) cantSk{
    UIAlertView* alertView = [[UIAlertView alloc] init];
    alertView.title = @"購入ができません";
    alertView.message = @"端末の機能制限でApp内での購入が不可になっています\n\n設定 > 一般 > 機能制限で\n[App内での購入]を\nONにしてください";
    [alertView addButtonWithTitle:@"OK"];
    [alertView show];
    
    [self cancel];
}

-(void) cancel{
    if(onCancel)
        onCancel();
    if(onComplete)
        onComplete();
}

// ▼ public =================================

-(void) purchase:(NSString*)productIdentifier{
    if(![self checkSk]){
        [self cantSk];
        return;
    }
    
    if([self isConnectiong]){
        NSLog(@"cancel on purchasing.....");
        [self cancel];
        
        return;
    }
    
    // プロダクト情報が既にあればそれを使う
    SKProduct* product = [productList objectForKey:productIdentifier];
    if(product != nil) {
        [self purchaseImple:product];
        return;
    }
    
    // なければとりにいく
    state = SLSK_PRODUCT_REQUEST;
    currentRequestType = SLSK_REQUEST_PURCHASE;
    [self productRequest:productIdentifier];
}

-(void) restore{
    if(![self checkSk]){
        [self cantSk];
        return;
    }
    
    if([self isConnectiong]){
        NSLog(@"cancel on purchasing.....");
        [self cancel];
        return;
    }
    
    state = SLSK_PRODUCT_REQUEST;
    currentRequestType = SLSK_REQUEST_RESTORE;
    [self restoreImple];
}

-(BOOL) checkSk{
    return [SKPaymentQueue canMakePayments];
}

-(BOOL) isConnectiong{
    return (state != SLSK_WAIT);
}

@end

参考

Blocks
僕はよくわからないまま使っているので、ちゃんと使いたい方は下記サイトがおすすめです

そんなiphone課金のデザインに興味のある(なくてもOK)デザイナー募集中!

現在スバコラボではデザイナーを【爆裂】で【激求】しています。
お気軽にご応募ください
ご応募はこちらから

0 件のコメント:

コメントを投稿