Amarronの日記

iOSやMac、Web系の記事を書きます。

iOS の アプリ内課金(In-App Purchase) 組込方法

概要

iPhone」や「iPad」でのアプリ内課金(In-App Purchase)の実装方法について書きました。
iOSアプリ内で特定の機能を有料販売するための準備・開発・テストの説明中心です。
全体的な作業時間としては、1日は覚悟したほうが良さそうです。
(課金のタイプによってはサーバー側の開発がないから、APNsよりは少し楽かも??)

開発環境

OS : OS X 10.9.2
Xcode : 5.1.1

前提条件

プロダクト(次の2つ)が作成されていること。

  • iOS Developer Centerでアプリの登録(Identifiersの登録時に「In-App Purchase」にチェックする(デフォでチェック入ってる))
  • iTunes Connectでアプリを作成(ステータスをPrepare for Uploadまで進める)

作成方法はこちら(アプリの登録(1.証明書関連の作成)、アプリを作成(3. 配布したいアプリの設定))

アプリ内課金の種類

全部で5種類。
消費型はゲームの課金でよく使われます。購読型は電子書籍でよく使われます。
今回は、「非消耗型(Non-consumable)プロダクト」で作成します。

名称 説明 備考
消耗型(Consumable)プロダクト アプリケーションの実行に伴って消費されていく項目です。Voice over IPアプリケーションで通信できる残り分数や、音声の転送など一度限りのサービスが例として挙げられます。 消費アイテム
非消耗型(Non-consumable)プロダクト ユーザのすべてのデバイス上で無制限に使用できる項目です。ユーザのすべてのデバイスで使用可能になります。例としては、書籍やゲームレベルなどのコンテンツ、およびアプリケーションの追加機能などがあります。 無制限アイテム
自動更新購読(Auto-renewable subscriptions) エピソードで構成されるコンテンツです。消耗型プロダクトと同様に、自動更新購読は、ユーザのすべてのデバイスで無制限に利用可能になります。非消耗型プロダクトと異なるのは、自動更新購読には期限があるという点です。新しいコンテンツは定期的に信され、ユーザは購読が有効な期間中、発行されたコンテンツに対してアクセスできます。自動更新購読の期限が近づいてくると、ユーザに代わってシステムにより購読が自動的に更新されます。 期間で自動課金
非更新購読(Non-renewable subscriptions) エピソードで構成されるコンテンツを含まない購読です。たとえば、歴史的な写真のデータベースに対するアクセス権や、フライトマップのコレクションなどがあります。ユーザのすべてのデバイスで購読を使用可能にし、ユーザの購入を復元するは、アプリケーション側で対応することになります。このプロダクトタイプは、ユーザのアカウントが既にサーバ上に存在し、このアカウントを使用してコンテンツの復元時にユーザを識別できる場合によく使用されます。購読の期限と期間もアプリケーション(またはサーバ)で実装し、実行することになります。 期間で課金(開発者が期間や期限をサーバーで管理)
無料購読(Free subscriptions) Newsstandに無料購読のコンテンツを置くための手段です。サインアップしたユーザは、Apple IDに関連付けられたどのデバイスからでも購読できます。期限切れになることはありません。また、購読にはNewsstand対応アプリケーションが必要です。 無料、Newsstand対応アプリケーション

流れ

  1. 準備
    1-1. iTunes ConnectでManage In-App Purchasesの追加
    1-2. StoreKitフレームワーク追加
    1-3. In-App PurchaseをON
  2. 開発
    2-1. チェック処理
    2-2. 購入開始処理
    2-3. トランザクション処理・リストア処理
    2-4. 購入終了処理
  3. テスト

1. 準備

iTunes Connectの設定やプロジェクトにフレームワークや設定の変更を行います。

1-1. iTunes ConnectでManage In-App Purchasesの追加

iTunes Connectに接続し対象のアプリを開き、アプリ内課金のアイテム情報(Manage In-App Purchases)を登録します。(登録内容は「2. 開発」で利用します。)
「Type」は、今回は「Non-consumable」を選択します。

f:id:Amarron:20140523212449p:plain

「Reference Name」は、アイテムの表示名を設定します。(Appleから毎月送信される売上レポートで表示名)
「Product ID」は、一意のグローバル識別子でドメイン名のスタイルを逆にして使用することを推奨しています(例: com.companyname.application.productid)。
「Screenshot」は、購入確認画面のスクリーンショットをアップロードします。(3. テスト実施時にスクリーンショットをキャプチャしアップロードしましょう。)

f:id:Amarron:20140523212507p:plain

1-2. StoreKitフレームワーク追加

ライブラリに「StoreKit.framework」を追加します。
(TAGETS > Build Phases > Link Binary With Libraries)

1-3. In-App PurchaseをON

CapabilitiesのIn-App PurchaseをOFFからONに変更します。 (PROJECT > Capabilities > In-App Purchase)

f:id:Amarron:20140523212521p:plain

2. 開発

アプリ内課金のプログラム。
参考URL:失敗しない iOS In-App Purchase プログラミング
サンプルコード:SampleInAppPurchaseswift

2-1. チェック処理

課金イベントにアプリ内課金が使えるかチェックをします。

MyClass.h

 #import <StoreKit/StoreKit.h>

// delegateにSKProductsRequestDelegateとSKPaymentTransactionObserverを追加
@interface MyClass : UIViewController <SKProductsRequestDelegate, 
                                            SKPaymentTransactionObserver> {
}

MyClass.m

// メソッド名は適当に
- (BOOL)_checkInAppPurchase
{
    if (![SKPaymentQueue canMakePayments]) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"エラー"
                                                        message:@"アプリ内課金が制限されています。"
                                                       delegate:nil
                                              cancelButtonTitle:nil
                                              otherButtonTitles:@"OK", nil];
        [alert show];
        return NO;
    }
    return YES;
}

2-2. 購入開始処理

アイテム情報の取得と購入開始の処理を行います。

MyClass.m

// メソッド名は適当に(チェック処理の結果がYESだったらこの処理を呼ぶ)
- (void)_startInAppPurchase
{
    // com.companyname.application.productidは、「1-1. iTunes ConnectでManage In-App Purchasesの追加」で作成したProduct IDを設定します。
    NSSet *set = [NSSet setWithObjects:@"com.companyname.application.productid", nil];
    SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
    productsRequest.delegate = self;
    [productsRequest start];
}

 #pragma mark SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
  // 無効なアイテムがないかチェック
  if ([response.invalidProductIdentifiers count] > 0) {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"エラー"
                                                        message:@"アイテムIDが不正です。"
                                                       delegate:nil
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil, nil];
    [alert show];
    return;
  }
  // 購入処理開始(「iTunes Storeにサインイン」ポップアップが表示)
  [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
  for (SKProduct *product in response.products) {
    SKPayment *payment = [SKPayment paymentWithProduct:product];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
  }
}

2-3. トランザクション処理・リストア処理

App Storeトランザクションを処理するのを待機し、購入が成功した場合アイテム購入の処理を行います。

MyClass.m

// トランザクション処理
#pragma mark SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                // NSLog(@"購入処理中");
                // TODO: インジケータなど回して頑張ってる感を出す。
                break;
            case SKPaymentTransactionStatePurchased:
                // NSLog(@"購入成功");
                // TODO: アイテム購入した処理(アップグレード版の機能制限解除処理等)
                // TODO: 購入の持続的な記録
                [queue finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                // NSLog(@"購入失敗: %@, %@", transaction.transactionIdentifier, transaction.error);
                // TODO: 失敗のアラート表示等
                break;
            case SKPaymentTransactionStateRestored:
                // リストア処理
                // NSLog(@"以前に購入した機能を復元");
                [queue finishTransaction:transaction];
                // TODO: アイテム購入した処理(アップグレード版の機能制限解除処理等)
                break;
            default:
                [queue finishTransaction:transaction];
                break;
        }
    }
}

// リストア処理結果
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
    // NSLog(@"リストア失敗:%@", error);
    // TODO: 失敗のアラート表示等
}

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    // NSLog(@"全てのリストア完了");
    // TODO: 完了のアラート表示等
}

購入の持続的な記録には次の方法があります。(消費型プロダクトは維持しません。)

  • Appレシートを使用した持続(iOS 7以降の非消耗型プロダクトと自動更新購読で推奨)
  • User DefaultsまたはiCloudを使用した値の持続
// User Defaultsを使用した値の持続の例)
MyClass.m

// 購入の持続的な記録
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL rocketCarEnabled = [defaults boolForKey:@"enable_rocket_car"];

// 購入アイテム使用時
if (rocketCarEnabled) {
 // ロケットカーを使用
} else {
 // 普通の自動車を使用
}

非消耗型プロダクトはAppレシートが推奨ですが、余力がなかったので記述は割愛します。

2-4. 購入終了処理

トランザクションオブザーバの削除を行います。(トランザクションが終了すると呼び出される)

MyClass.m
#pragma mark SKPaymentQueue
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions
{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

3. テスト

テストユーザーの作成と実機で確認します。
参考URL:iPad上でアプリ内課金 (In-App Purchase) の実機テストをする方法メモ

  1. iTunes Connectでテストユーザを作成します。(10 Minute Mailを使うと10分だけ無料でメアドが貰えるので便利かも)
  2. 実機の端末App IDをサインアウトし、アプリをインストールします。
  3. 追加した課金のシステムをテストします。テスト実施(課金イベント)時にサインインを求められるので、テストユーザーでサインインします。
  4. 課金成功のメッセージが表示されれば、無事課金完了です。

テスト実施時の注意事項
参考URL: iOSのアプリ内課金(In App Purchase)での注意点

  • テスト用のiTunesアカウントで事前にサインインしない
  • Appleサーバーに対してレシートの有効性を確認する場合には本番用URL -> サンドボックスURLの順で検証する

以上でアプリ内課金(In-App Purchase)の組込作業は終わりです。
お疲れ様でした!