Xamarin.iOS での StoreKit の概要と製品情報の取得
アプリ内購入のユーザー インターフェイスは、次のスクリーンショットに示されています。 トランザクションが発生する前に、アプリケーションは製品の価格と説明を表示用に取得する必要があります。 次に、ユーザーが [Buy]\(購入\) を押すと、アプリケーションは StoreKit に要求を行い、StoreKit が確認ダイアログと Apple ID ログインを管理します。 トランザクションが成功したと仮定すると、StoreKit はアプリケーション コードを通知します。これにより、トランザクションの結果が格納され、ユーザーに購入へのアクセス権が付与される必要があります。
クラス
アプリ内購入を実装するには、StoreKit フレームワークの次のクラスが必要です。
SKProductsRequest – 承認された販売する製品の StoreKit への要求 (App Store)。 多数の製品 ID を使用して構成できます。
- SKProductsRequestDelegate – 製品の要求と応答を処理するメソッドを宣言します。
- SKProductsResponse – StoreKit (App Store) からデリゲートに返送されます。 要求と共に送信された製品 ID と一致する SKProducts が含まれます。
- SKProduct – StoreKit から取得した製品 (iTunes Connect で構成)。 製品 ID、タイトル、説明、価格など、製品に関する情報が含まれます。
- SKPayment – 製品 ID を使用して作成され、購入を実行するために支払いキューに追加されます。
- SKPaymentQueue – Apple に送信されるキューに登録された支払い要求。 通知は、各支払いが処理された結果としてトリガーされます。
- SKPaymentTransaction – 完了したトランザクション (App Store によって処理され、StoreKit 経由でアプリケーションに返送された購入要求) を表します。 トランザクションは、購入済み、復元済み、または失敗になる可能性があります。
- SKPaymentTransactionObserver – StoreKit 支払いキューによって生成されたイベントに応答するカスタム サブクラスです。
- StoreKit 操作は非同期 – SKProductRequest が開始されるか、SKPayment がキューに追加されると、コードに制御が返されます。 StoreKit は、Apple のサーバーからデータを受信するときに、SKProductsRequestDelegate または SKPaymentTransactionObserver サブクラスのメソッドを呼び出します。
次の図は、さまざまな StoreKit クラス間のリレーションシップを示しています (抽象クラスはアプリケーションで実装する必要があります)。
これらのクラスについては、このドキュメントの後半で詳しく説明します。
テスト
ほとんどの StoreKit 操作では、テストに実際のデバイスが必要です。 製品情報 (価格と説明など) の取得はシミュレーターで機能しますが、購入操作と復元操作ではエラーが返されます (FailedTransaction Code=5002 不明なエラーが発生しました)。
注: StoreKit は iOS シミュレーターでは動作しません。 iOS シミュレーターでアプリケーションを実行している場合、アプリケーションが支払いキューを取得しようとすると、StoreKit によって警告がログに記録されます。 ストアのテストは、実際のデバイスで行う必要があります。
重要: 設定アプリケーションにテスト アカウントでサインインしないでください。 設定アプリケーションを使用して既存の Apple ID アカウントからサインアウトすることができます。その後、テスト用の Apple ID を使用してログインするには、アプリ内購入シーケンス内でメッセージが表示されるのを待つ必要があります。
テスト アカウントを使用して実際のストアにサインインしようとすると、実際の Apple ID に自動的に変換されます。 そのアカウントはテストには使用できなくなります。
StoreKit コードをテストするには、通常の iTunes テスト アカウントからログアウトし、テスト ストアにリンクされている特別なテスト アカウント (iTunes Connect で作成) でログインする必要があります。 現在のアカウントからサインアウトするには、次に示すように、[設定] > [iTunes and App Store]\(iTunes と App Store\) にアクセスします。
次に、アプリ内の StoreKit から要求されたら、テスト アカウントでサインインします。
iTunes Connect でテスト ユーザーを作成するには、メイン ページの [Users and Roles]\(ユーザーとロール\) をクリックします。
[Sandbox Testers]\(サンドボックス テスター\) を選択します。
既存のユーザーの一覧が表示されます。 新しいユーザーを追加することも、既存のレコードを削除することもできます。 ポータルでは (現在) 既存のテスト ユーザーを表示または編集できないため、作成された各テスト ユーザー (特に割り当てたパスワード) を適切に記録しておくことをお勧めします。 テスト ユーザーを削除すると、メール アドレスを別のテスト アカウントに再利用することはできません。
新しいテスト ユーザーには、実際の Apple ID (名前、パスワード、シークレットの質問、回答など) と同様の属性があります。 ここに入力したすべての詳細を記録しておきます。 [Select iTunes Store]\(iTunes ストアの選択\) フィールドでは、そのユーザーとしてログインするときにアプリ内購入でどの通貨と言語を使用するかを決定します。
製品情報の取得
アプリ内購入製品を販売する最初の手順は、App Store から現在の価格と説明を取得して表示することです。
アプリが販売する製品の種類 (コンシューマブル、非コンシューマブル、またはサブスクリプションの種類) に関係なく、表示用の製品情報を取得するプロセスは同じです。 この記事に付属する InAppPurchaseSample コードには、表示用の運用情報を取得する方法を示す Consumables という名前のプロジェクトが含まれています。 次の方法がわかります。
SKProductsRequestDelegate
の実装を作成し、ReceivedResponse
抽象メソッドを実装します。 このコード例では、これをInAppPurchaseManager
クラスと呼びます。- StoreKit で支払いが許可されているかどうかを確認します (
SKPaymentQueue.CanMakePayments
を使用)。 - iTunes Connect で定義されている製品 ID を使用して
SKProductsRequest
をインスタンス化します。 これは、例のInAppPurchaseManager.RequestProductData
メソッドで行います。 SKProductsRequest
で Start メソッドを呼び出します。 これにより、App Store サーバーへの非同期呼び出しがトリガーされます。 デリゲート (InAppPurchaseManager
) は結果と共にコールバックされます。- デリゲート (
InAppPurchaseManager
) のReceivedResponse
メソッドは、App Store から返されたデータ (製品の価格と説明、または無効な製品に関するメッセージ) で UI を更新します。
全体的な相互作用は次のようになります (StoreKit は iOS に組み込まれており、App Store は Apple のサーバーを表します)。
製品情報の表示の例
Consumables サンプルコードは、製品情報を取得する方法を示しています。 サンプルのメイン画面には、App Store から取得された 2 つの製品の情報が表示されます。
製品情報を取得して表示するサンプル コードについて、以下で詳しく説明します。
ViewController メソッド
ConsumableViewController
クラスは、製品 ID がクラスにハードコーディングされている 2 つの製品の価格の表示を管理します。
public static string Buy5ProductId = "com.xamarin.storekit.testing.consume5credits",
Buy10ProductId = "com.xamarin.storekit.testing.consume10credits";
List<string> products;
InAppPurchaseManager iap;
public ConsumableViewController () : base()
{
// two products for sale on this page
products = new List<string>() {Buy5ProductId, Buy10ProductId};
iap = new InAppPurchaseManager();
}
クラス レベルでは、NSNotificationCenter
オブザーバーのセットアップに使用される NSObject も宣言されている必要があります。
NSObject priceObserver;
ViewWillAppear メソッドでは、オブザーバーが作成され、既定の通知センターを使用して割り当てられます。
priceObserver = NSNotificationCenter.DefaultCenter.AddObserver (
InAppPurchaseManager.InAppPurchaseManagerProductsFetchedNotification,
(notification) => {
// display code goes here, to handle the response from the App Store
}
ViewWillAppear
メソッドの最後で、RequestProductData
メソッドを呼び出して StoreKit 要求を開始します。 この要求が行われると、StoreKit は Apple のサーバーに非同期的に接続して情報を取得し、アプリにフィードバックします。 これは、次のセクションで説明する SKProductsRequestDelegate
サブクラス (InAppPurchaseManager
) によって実現されます。
iap.RequestProductData(products);
価格と説明を表示するコードは、SKProduct から情報を取得して UIKit コントロールに割り当てるだけです (LocalizedTitle
と LocalizedDescription
が表示され、StoreKit はユーザーのアカウント設定に基づいて正しいテキストと価格を自動的に解決します)。 次のコードは、上記で作成した通知に属しています。
priceObserver = NSNotificationCenter.DefaultCenter.AddObserver (
InAppPurchaseManager.InAppPurchaseManagerProductsFetchedNotification,
(notification) => {
// display code goes here, to handle the response from the App Store
var info = notification.UserInfo;
if (info.ContainsKey(NSBuy5ProductId)) {
var product = (SKProduct) info.ObjectForKey(NSBuy5ProductId);
buy5Button.Enabled = true;
buy5Title.Text = product.LocalizedTitle;
buy5Description.Text = product.LocalizedDescription;
buy5Button.SetTitle("Buy " + product.Price, UIControlState.Normal); // price display should be localized
}
}
最後に、ViewWillDisappear
メソッドはオブザーバーが削除されていることを確認する必要があります。
NSNotificationCenter.DefaultCenter.RemoveObserver (priceObserver);
SKProductRequestDelegate (InAppPurchaseManager) メソッド
RequestProductData
メソッドは、アプリケーションが製品の価格やその他の情報を取得する場合に呼び出されます。 製品 ID のコレクションを正しいデータ型に解析し、その情報を使用して SKProductsRequest
を作成します。 Start メソッドを呼び出すと、Apple のサーバーに対してネットワーク要求が行われます。 要求は非同期的に実行され、正常に完了すると、デリゲートの ReceivedResponse
メソッドが呼び出されます。
public void RequestProductData (List<string> productIds)
{
var array = new NSString[productIds.Count];
for (var i = 0; i < productIds.Count; i++) {
array[i] = new NSString(productIds[i]);
}
NSSet productIdentifiers = NSSet.MakeNSObjectSet<NSString>(array);
productsRequest = new SKProductsRequest(productIdentifiers);
productsRequest.Delegate = this; // for SKProductsRequestDelegate.ReceivedResponse
productsRequest.Start();
}
iOS は、アプリケーションが実行されているプロビジョニング プロファイルに応じて、App Store の "サンドボックス" または "実稼働" バージョンに要求を自動的にルーティングします。そのため、アプリを開発またはテストするときに、iTunes Connect で構成されているすべての製品 (Apple によってまだ送信または承認されていない製品も含む) に要求がアクセスできるようになります。 アプリケーションが運用環境にある場合、StoreKit 要求は承認済み製品の情報のみを返します。
オーバーライドされた ReceivedResponse
メソッドは、Apple のサーバーがデータで応答した後に呼び出されます。 これはバックグラウンドで呼び出されるため、コードは有効なデータを解析し、通知を使用して、その通知を "リッスン" している ViewController に製品情報を送信する必要があります。 有効な製品情報を収集して通知を送信するコードを次に示します。
public override void ReceivedResponse (SKProductsRequest request, SKProductsResponse response)
{
SKProduct[] products = response.Products;
NSDictionary userInfo = null;
if (products.Length > 0) {
NSObject[] productIdsArray = new NSObject[response.Products.Length];
NSObject[] productsArray = new NSObject[response.Products.Length];
for (int i = 0; i < response.Products.Length; i++) {
productIdsArray[i] = new NSString(response.Products[i].ProductIdentifier);
productsArray[i] = response.Products[i];
}
userInfo = NSDictionary.FromObjectsAndKeys (productsArray, productIdsArray);
}
NSNotificationCenter.DefaultCenter.PostNotificationName (InAppPurchaseManagerProductsFetchedNotification, this, userInfo);
}
図には示されていませんが、App Store サーバーに到達できない場合 (またはその他のエラーが発生した場合) にユーザーになんらかのフィードバックを提供できるように、RequestFailed
メソッドもオーバーライドする必要があります。 サンプル コードはコンソールに書き込むだけですが、実際のアプリケーションでは、error.Code
プロパティに対してクエリを実行し、カスタム動作 (ユーザーへのアラートなど) を実装することを選択する場合があります。
public override void RequestFailed (SKRequest request, NSError error)
{
Console.WriteLine (" ** InAppPurchaseManager RequestFailed() " + error.LocalizedDescription);
}
このスクリーンショットは、読み込み直後のサンプル アプリケーションを示しています (製品情報が利用できない場合)。
無効な製品
SKProductsRequest
は、無効な製品 ID の一覧を返す場合もあります。 無効な製品は、通常、次のいずれかが原因で返されます。
製品 ID の入力が間違っている – 有効な製品 ID のみが受け入れられます。
製品が承認されていない - テスト中は、販売対象としてクリアされたすべての製品が SKProductsRequest
によって返される必要があります。ただし、運用環境では承認済み製品のみが返されます。
アプリ ID が明示的でない - ワイルドカード アプリ ID (アスタリスクを使用) では、アプリ内購入は許可されません。
プロビジョニング プロファイルが正しくない – プロビジョニング ポータルでアプリケーション構成を変更する場合 (アプリ内購入の有効化など)、正しいプロビジョニング プロファイルを再生成し、アプリのビルド時に使用することを忘れないでください。
iOS 有料アプリケーションの契約が適用されていない - Apple 開発者アカウントの有効な契約がない限り、StoreKit の機能はまったく機能しません。
バイナリの状態が拒否済みになっている – 以前に送信されたバイナリが (App Store チームまたは開発者のいずれかによって) 拒否済みの状態になっている場合、StoreKit は機能しません。
サンプル コードの ReceivedResponse
メソッドは、無効な製品をコンソールに出力します。
public override void ReceivedResponse (SKProductsRequest request, SKProductsResponse response)
{
// code removed for clarity
foreach (string invalidProductId in response.InvalidProducts) {
Console.WriteLine("Invalid product id: " + invalidProductId );
}
}
ローカライズされた価格の表示
価格レベルは、各国の App Store 全体で各製品に対して特定の価格を指定します。 通貨ごとに価格が正しく表示されるようにするには、SKProduct
ごとの Price プロパティではなく、次の拡張メソッド (SKProductExtension.cs
で定義) を使用します。
public static class SKProductExtension {
public static string LocalizedPrice (this SKProduct product)
{
var formatter = new NSNumberFormatter ();
formatter.FormatterBehavior = NSNumberFormatterBehavior.Version_10_4;
formatter.NumberStyle = NSNumberFormatterStyle.Currency;
formatter.Locale = product.PriceLocale;
var formattedString = formatter.StringFromNumber(product.Price);
return formattedString;
}
}
ボタンの Title を設定するコードでは、次のような拡張メソッドが使用されます。
string Buy = "Buy {0}"; // or a localizable string
buy5Button.SetTitle(String.Format(Buy, product.LocalizedPrice()), UIControlState.Normal);
2 つの異なる iTunes テスト アカウント (1 つはアメリカのストア用、もう 1 つは日本のストア用) を使用すると、次のスクリーンショットが表示されます。
ストアは製品情報に使用される言語と価格通貨に影響し、デバイスの言語設定はラベルやその他のローカライズされたコンテンツに影響します。
別のストア テスト アカウントを使用するには、[設定] > [iTunes and App Store]\(iTunes と App Store\) でサインアウトし、アプリケーションを再開して別のアカウントでサインインする必要があることを思い出してください。 デバイスの言語を変更するには、[設定] > [一般] > [地域と言語] > [言語] に移動します。