StoreKit 概觀和擷取 Xamarin.iOS 中的產品資訊
應用程式內購買的使用者介面會顯示在下列螢幕快照中。 在進行任何交易發生之前,應用程式必須擷取產品的價格和顯示描述。 然後當使用者按下 Buy 時,應用程式會向 StoreKit 提出要求,以管理確認對話方塊和 Apple ID 登入。 假設交易成功,StoreKit 會通知應用程式程式代碼,此程式代碼必須儲存交易結果,併為使用者提供購買的存取權。
類別
實作應用程式內購買需要 StoreKit 架構的下列類別:
SKProductsRequest – 要求 StoreKit 銷售已核准的產品(App Store)。 可以使用數個產品標識碼來設定。
- SKProductsRequestDelegate – 宣告處理產品要求和回應的方法。
- SKProductsResponse – 從 StoreKit (App Store) 傳回委派。 包含符合隨要求傳送的產品標識碼的 SKProducts。
- SKProduct – 從 StoreKit 擷取的產品(您已在 iTunes 連線 中設定)。 包含產品的相關信息,例如產品標識碼、標題、描述和價格。
- SKPayment – 以產品標識元建立, 並新增至付款佇列以執行購買。
- 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 連線 中建立)。 若要註銷目前的帳戶,請流覽 設定 > iTunes 和 App Store,如下所示:
然後在應用程式內由 StoreKit 要求時,使用測試帳戶登入:
若要在iTunes 中建立測試使用者 連線 請按兩下主頁面上的 [使用者和角色]。
選取 沙箱測試人員
現有的使用者清單隨即顯示。 您可以新增使用者或刪除現有的記錄。 入口網站不會 (目前) 可讓您檢視或編輯現有的測試使用者,因此建議您保留所建立之每個測試使用者的良好記錄(特別是您指派的密碼)。 刪除測試用戶之後,就無法將電子郵件位址重新用於另一個測試帳戶。
新的測試使用者具有與真實 Apple ID 類似的屬性(例如名稱、密碼、秘密問題和答案)。 記錄這裡輸入的所有詳細數據。 [ 選取 iTunes 市集 ] 字段會決定當以該使用者身分登入時,應用程式內購買將會使用的貨幣和語言。
擷取產品資訊
銷售應用程式內購買產品的第一個步驟是顯示:從 App Store 擷取目前的價格和描述以顯示。
無論應用程式銷售的產品類型為何(消費性、非消費性或訂用帳戶類型),擷取顯示產品資訊的程式都相同。 本文隨附的 InAppPurchaseSample 程式代碼包含名為 Consumables 的專案,示範如何擷取生產資訊以供顯示。 其示範如何:
- 建立的實作
SKProductsRequestDelegate
,並實作ReceivedResponse
抽象方法。 範例程式代碼會呼叫這個InAppPurchaseManager
類別。 - 請洽詢 StoreKit,以查看是否允許付款(使用
SKPaymentQueue.CanMakePayments
)。 - 使用 iTunes 連線 中定義的產品識別碼具現化
SKProductsRequest
。 這會在範例的InAppPurchaseManager.RequestProductData
方法中完成。 - 在上
SKProductsRequest
呼叫 Start 方法。 這會觸發對 App Store 伺服器的異步呼叫。 委派 (InAppPurchaseManager
) 將會以結果重新呼叫。 - 委派的 (
InAppPurchaseManager
)ReceivedResponse
方法會使用 App Store 傳回的數據來更新 UI(產品價格和描述,或無效產品的訊息)。
整體互動看起來像這樣( StoreKit 是 iOS 內建的,而 App Store 代表 Apple 的伺服器):
顯示產品資訊範例
消費 性產品 範例程式代碼示範如何擷取產品資訊。 範例的主畫面會顯示從 App Store 擷取的兩個產品資訊:
以下更詳細地說明擷取和顯示產品資訊的範例程序代碼。
ViewController 方法
類別 ConsumableViewController
會管理類別中已硬式編碼之兩個產品的價格顯示。
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();
}
在類別層級,也應該宣告 NSObject,以用來設定 NSNotificationCenter
觀察者:
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
當應用程式想要擷取產品價格和其他資訊時,會呼叫 方法。 它會將 Product Ids 的集合剖析為正確的資料類型,然後使用該資訊建立 SKProductsRequest
。 呼叫 Start 方法會導致對 Apple 的伺服器提出網路要求。 要求將會以異步方式執行,並在成功完成時呼叫 ReceivedResponse
Delegate 的 方法。
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 連線 中設定的每個產品(即使是尚未提交或核准 Apple 的產品)。 當您的應用程式在生產環境中時,StoreKit 要求只會傳回已核准產品的資訊。
ReceivedResponse
覆寫的方法會在 Apple 的伺服器回應數據之後呼叫。 由於這會在背景中呼叫,因此程式代碼應該剖析有效的數據,並使用通知,將產品資訊傳送給正在「接聽」該通知的任何 ViewControllers。 收集有效產品資訊並傳送通知的程序代碼如下所示:
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);
}
雖然圖表中未顯示,但也應該覆寫 方法, RequestFailed
以便在 App Store 伺服器無法連線時提供一些意見反應給使用者(或發生其他錯誤)。 範例程式代碼只會寫入主控台,但實際的應用程式可能會選擇查詢屬性 error.Code
並實作自定義行為(例如對使用者的警示)。
public override void RequestFailed (SKRequest request, NSError error)
{
Console.WriteLine (" ** InAppPurchaseManager RequestFailed() " + error.LocalizedDescription);
}
這個螢幕快照會在載入之後立即顯示範例應用程式(未提供產品資訊時):
無效的產品
SKProductsRequest
也可能傳回無效的產品標識碼清單。 由於下列其中一項原因,通常會傳回無效的產品:
產品識別碼的類型錯誤 – 只接受有效的產品識別碼。
產品尚未核准 – 測試時,所有已清除銷售的產品都應該由 SKProductsRequest
傳回;但在生產環境中,只會傳回已核准的產品。
應用程式標識碼不明確 – 通配符應用程式標識碼(含星號)不允許應用程式內購買。
布建配置檔 不正確 – 如果您在布建入口網站中變更應用程式組態(例如啟用應用程式內購買),請記得在建置應用程式時重新產生並使用正確的布建配置檔。
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 的每個產品指定特定價格。 若要確保每個貨幣的價格都正確顯示,請使用下列擴充方法(定義於 SKProductExtension.cs
中),而不是每個 SKProduct
的 Price 屬性:
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;
}
}
設定按鈕標題的程式代碼會使用如下的擴充方法:
string Buy = "Buy {0}"; // or a localizable string
buy5Button.SetTitle(String.Format(Buy, product.LocalizedPrice()), UIControlState.Normal);
使用兩個不同的 iTunes 測試帳戶(一個用於美國商店,另一個用於日本商店)會產生下列螢幕快照:
請注意,市集會影響用於產品資訊和價格貨幣的語言,而裝置的語言設定會影響標籤和其他當地語系化內容。
回想一下,若要使用不同的市集測試帳戶,您必須在 設定 > iTunes 和 App Store 中註銷,然後重新啟動應用程式以使用不同的帳戶登入。 若要變更裝置的語言,請移至 設定 > 一般>國際>語言。