iOS : アプリ内課金(In-App Purchase)をXcodeでやってみる

広告:超オススメUnity Asset
  広告:超オススメUnity Asset

アプリ内課金(In-App Purchase)については、「In-App Purchase プログラミングガイド(日本語)」に詳しい解説が載っています。このMEMOでは、Xcode4.3.3を使って、ここに書いてある例の通りにiOSアプリ内から購入ダイアログを出すところまでやってみたいと思います。
また、アプリ内課金ではいくつかのタイプがあるのですが、ここでは機能解除を有料販売するもの(アプリ自体は無料で配布し、基本機能は無料で使えるようにしておいて、特定の機能をアプリ内で有料販売し、購入後、全機能使えるようにするようなタイプ)を作ります。

実際にアプリにプログラムを組み込む例は「In-App Purchase プログラミングガイド」の20ページ目に手順が書いてあります。この手順に沿って(少し応用していますが)「InAppPurchaseManager」というクラスを用意し、それをアプリから利用する形にしています。

このMEMOの流れとしては、こんな感じ。

  1. XcodeのテンプレートからシンプルなiPhoneアプリを作る
  2. StoreKitフレームワークをプロジェクトに追加
  3. InAppPurchaseManagerクラスを作成
  4. 「Buy」ボタンを用意し、押すと課金処理開始
  5. シミュレーターで動作確認し、invalid product identifierを確認
  6. iTunes Connectでアプリ内課金用のプロダクトを準備
  7. 再度シミュレーターで動作確認し、購入ダイアログが出るところまで確認

※購入ダイアログが出るところまではシミュレーターで確認できますが、実際に購入するテストに関しては、iTunes Connectでテストユーザを使って、実機で動作確認をする必要があります。

XcodeのテンプレートからシンプルなiPhoneアプリを作る

  1. Xcodeを起動し、テンプレートから「Single View Application」を選択。
  2. Product Nameに「InAppPurchaseXcode」と、Company Identifierに「com.mushikago」と、Class Prefixに「IAP」と入力し、Device Familyはシンプルにするために「iPhone」のみ、Use Storyboardsにチェック。(入力欄はいずれも任意の文字列です)
    次の画面で、任意の場所にプロジェクトを保存。

StoreKitフレームワークをプロジェクトに追加

  1. 「TARGETS」のSummaryタブの中央あたり「Linked Frameworks and Libraries」の「+」を押して「StoreKit.framework」を追加。

InAppPurchaseManagerクラスを作成

  1. プロジェクトのコンテキストメニューから「New File…」を選択。
  2. Objective-C class」を選択。
  3. NSObject」のサブクラスとして「InAppPurchaseManager」を作成。
  4. 「InAppPurchaseManager.h」と「InAppPurchaseManager.m」がそれぞれ下記のようになるように。
    ※デバッグに使ったNSLogや、プログラミングガイドのどのあたりに解説があるかをコメントしておきました。

    InAppPurchaseManager.h

    [sourcecode language=”objc”]
    #import <UIKit/UIKit.h>
    #import <StoreKit/StoreKit.h>

    @interface InAppPurchaseManager : NSObject <SKProductsRequestDelegate,SKPaymentTransactionObserver>
    {
    //SKProduct *myProduct;
    SKProductsRequest *myProductRequest;
    }

    //public method
    – (BOOL)canMakePurchases;
    – (void)requestProductData:(NSString *) pID;
    @end
    [/sourcecode]

    InAppPurchaseManager.m
    #import "InAppPurchaseManager.h"
    //#define kInAppPurchaseProUpgradeProductId @"com.mushikago.InAppPurchaseXcode.unLock"
    //#define kAppProductId @"com.mushikago.InAppPurchaseXcode"
    
    @implementation InAppPurchaseManager 
    
    
    // 「3. ペイメント処理を進めてよいか判断します。」部分
    - (BOOL)canMakePurchases
    {
        if([SKPaymentQueue canMakePayments])
        {
            return TRUE;
        }else {
            return FALSE;
        }
    }
    
    // 「4. プロダクトに関する情報を取得します。」部分
    - (void)requestProductData:(NSString *) pID
    {
        myProductRequest= [[SKProductsRequest alloc]
                                     initWithProductIdentifiers: [NSSet setWithObject: pID]];
        myProductRequest.delegate = self;
        [myProductRequest start];
    }
    
    - (void)productsRequest:(SKProductsRequest *)request
         didReceiveResponse:(SKProductsResponse *)response
    {
        
        if (response == nil) {
            [self showAlert:@"responseなし"];
            return;
        }
        
        // 確認できなかったidentifierをログに記録
        for (NSString *identifier in response.invalidProductIdentifiers) {
            NSLog(@"invalid product identifier: %@", identifier);
        }
        
        //NSArray *products = response.products; // UIを埋める
        //NSString *message = [NSString stringWithFormat:@"配列は%d個です。", [products count]];
        
        //NSString *message = @"何か戻りました。";
        //NSLog(@"配列は%d個です。", [products count]);
        //[self showAlert:message];
        
        
        //[myProductRequest autorelease];
        
        
        //Observer
        //InAppPurchaseStoreObserver *observer = [[InAppPurchaseStoreObserver alloc]init];
        //[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
        
        //  「6. トランザクションオブザーバをペイメントキューに登録します。」部分
        // (※本当は起動時の方がいい)
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        
        for (SKProduct *product in response.products ) {
            NSLog(@"valid product identifier: %@", product.productIdentifier);
            
            NSLog(@"%@",[product productIdentifier]);
            NSLog(@"%@",[product localizedTitle]);
            NSLog(@"%@",[product localizedDescription]);
            
            //SKPayment *payment = [SKPayment paymentWithProduct:product];
            //[[SKPaymentQueue defaultQueue] addPayment:payment];
            
            // 「11. ・・・ユーザ がStore内のアイテムを選択したら、ペイメントオブジェクトを作成し、ペイメントキューに追加 します。」部分
            SKPayment *payment = [SKPayment paymentWithProduct:product];
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
    
        
    }
    
    // 「7. MyStoreObserverにpaymentQueue:updatedTransactions:メソッドを実装します。」部分
    - (void)paymentQueue:(SKPaymentQueue *)queue
      updatedTransactions:(NSArray *)transactions
    {
        for (SKPaymentTransaction *transaction in transactions)
        {
            switch (transaction.transactionState)
            {
                case SKPaymentTransactionStatePurchasing:
                    // 購入処理中。基本何もしなくてよい。処理中であることがわかるようにインジケータをだすなど。
                    break;
                case SKPaymentTransactionStatePurchased:
                    // 購入処理成功
                    [self showAlert:@"購入処理成功!"];
                    NSLog(@"購入処理成功!");
                    NSLog(@"transaction : %@",transaction);
                    NSLog(@"transaction.payment.productIdentifier : %@",transaction.payment.productIdentifier);
                    [queue finishTransaction: transaction];
                    break;
                case SKPaymentTransactionStateFailed:
                    // 購入処理失敗。ユーザが購入処理をキャンセルした場合もここ
                    if (transaction.error.code != SKErrorPaymentCancelled)
                    {
                        // 必要に応じてここでエラーを表示する
                         [self showAlert:[transaction.error localizedDescription]];
                        NSLog(@"購入処理失敗。");
                        NSLog(@"error : %@",[transaction.error localizedDescription]);
                    }
                   [queue finishTransaction:transaction];
                    break;
                case SKPaymentTransactionStateRestored:
                    //リストア処理開始
                    [self showAlert:@"リストア処理開始"];
                    NSLog(@"リストア処理");
                    NSLog(@"transaction : %@",transaction);
                    NSLog(@"transaction.originalTransaction.payment.productIdentifier : %@",transaction.originalTransaction.payment.productIdentifier);
                    [queue finishTransaction:transaction];
                default:
                    break;
            } }
    }
    
    
    
    
    //Alert
    - (void)showAlert:(NSString *)msg
    {
        UIAlertView *alert = [ [UIAlertView alloc] initWithTitle:@"Message"
                                                         message:msg
                                                        delegate:self
                                               cancelButtonTitle:@"OK"
                                               otherButtonTitles:nil];
        [alert show];
    }
    @end
    

「Buy」ボタンを用意し、押すと課金処理開始

  1. MainStoryboard.storyboard」を選択し、Round Rect Buttonを配置。
  2. Show the Assistant editor」(右上のEditorの真ん中のアイコン)を開いて、ストーリーボードのボタン上をcontrolキーを押しながらドラッグし、画面右「IAPViewController.h」の「@interface」の下に挿入。
  3. 挿入時に現れるダイアログ内「Connection」項目を「Action」に変更し、「Name」を「buyButton」に。
  4. 「IAPViewController.h」で、「InAppPurchaseManager.h」をimportし、「appMng」という名のInAppPurchaseManagerのインスタンスを定義。

    [sourcecode language=”objc”]
    #import <UIKit/UIKit.h>
    #import "inAppPurchaseManager.h"

    @interface IAPViewController : UIViewController
    {
    InAppPurchaseManager *appMng;
    }
    – (IBAction)buyButton:(id)sender;

    @end
    [/sourcecode]


    ※このスクリーンショットでは「.m」が選択されているように見えますが、「.h」の方です。

  5. 「IAPViewController.m」のbuyButtonのsenderを以下の内容に書き換え。
    //////ここから/////
    
    - (IBAction)buyButton:(id)sender {
        appMng = [[InAppPurchaseManager alloc]init];
        //BOOL a = appMng.canMakePurchases;
        //NSLog(@"課金:%d",a);
        
        if(appMng.canMakePurchases)
        {
            [appMng requestProductData:@"com.mushikago.InAppPurchaseXcode.unLock"];
        }else {
            [self showAlert:@"課金が許可されていません。"];
        }
        
    }
    
    - (void)showAlert:(NSString *)msg
    {
        UIAlertView *alert = [ [UIAlertView alloc] initWithTitle:@"Info"
                                                         message:msg
                                                        delegate:self
                                               cancelButtonTitle:@"OK"
                                               otherButtonTitles:nil];
        [alert show];
    }
    
    /////ここまで/////
    

シミュレーターで動作確認し、invalid product identifierを確認

  1. iPhone 5.1 Simulator」を選択して、シミュレーターを起動・確認。
  2. ボタンを押すと購入処理が始まるものの、「invalid product identifier」と表示され、プロダクトIDが正しくない旨のメッセージが表示される。

エラーが表示されたのは、アプリ内課金用のプロダクトを準備していないからで、これは購入するもの(この例では、com.mushikago.InAppPurchaseXcode.unLockというIDのプロダクト)が存在しないので、当然の結果です。
※追記:自分のアプリでないものでも存在するとinvalidとエラーが返らない可能性あり。
次にこのプロダクトを準備します。

iTunes Connectでアプリ内課金用のプロダクトを準備

  1. iTunes Connectの「Manage Your Applications」で「com.mushikago.InAppPurchaseXcode(任意)」というバンドルIDのアプリを用意します。
    これは、XcodeのTARGETS>Summaryにある「Bundle Identifier」と同じIDです。
  2. iTunes Connectの「Contracts, Tax, and Banking」の設定が済んでいたら、アプリ詳細で「Manage In-App Purchases」が設定できるようになっているので選択。
  3. Select Type」では、「Non-Consumable」を選択。これはロック解除機能のように、そのアプリに一度だけ購入できるようなタイプのプロダクトです。
  4. アプリ内課金用のプロダクトの名前や価格などの詳細を入力。大事なところは「Product ID」欄で、ここにアプリのボタンを押した時に呼び出される「buyButton:(id)sender」で指定している文字列「com.mushikago.InAppPurchaseXcode.unLock」(任意)を入力します。つまり、ボタンを押すとここで指定したProduct IDのプロダクトを購入するという言うことになります。

再度シミュレーターで動作確認し、購入ダイアログが出るところまで確認

再度、Xcodeに戻って、「iPhone 5.1 Simulator」を選択して、シミュレーターを起動・確認し、Buyボタンを押すと、通信が始まり、しばらくすると購入用ダイアログが表示されます。確認はダイアログが表示されるまでにしてキャンセルしてください。実際に「購入」をテストするには、iTunes Connectでテストユーザを作り、実機で確認する必要があります。(実際のAppleIDを使わないように!テストユーザでログインし直すには、iPhone実機の設定>Storeの最下部のApple IDをクリックしてサインアウトします。)また、このタイプのプロダクトは一度購入すると、そのユーザではその後「購入済み」となってしまいます。再度、購入のテストをするためには、新たにテストユーザを作ってテストするようになりますので、「購入した場合」をテストする必要がない場合は購入ダイアログを出すまでにしてキャンセルするのがいいと思います。

※テストユーザは、実在するメールアドレスである必要はありませんが、他の開発者と被らないように独自ドメインのものにしておいた方がいいです。

お疲れ様でした!以上で、Xcodeを使ったアプリ内課金の第一歩までいけたと思います。


追記:
Xcode 4.4でもほぼ同様の流れで作業可能な事を確認済み。

追記(2012.10.05):まとめスライドを公開しました。

コメント

  1. […] 実機での動作確認が必要です。また、「iOS : アプリ内課金(In-App Purchase)をXcodeでやってみる」の「iTunes Connectでアプリ内課金用のプロダクトを準備」にあるように購入するアプリ内課金 […]

  2. […] 「iOS : アプリ内課金(In-App Purchase)をXcodeでやってみる」で作成したクラスを読み込みます。 プロジェクトのコンテキストメニューから「Add Files to “HelloWorldANE”…」を選択し […]

  3. […] make in-app purchase http://mushikago.com/i/?p=1114 いいね:いいね 読み込み中… カテゴリー: iPhoneApp | 投稿日: 2013年8月22日 | 投稿者: isa […]