繼前一篇 – 安裝篇完成後, 來到較核心的實作部分, app 前端的實作流程.
本篇內容包含: Unity IAP 流程/回呼結構, 實作 IStoreListener 回呼介面: 初始化與購買結果. 更詳細的也可以官方查看更詳細內容.
以下為相關版本:
Unity: 2019.2.14f
Unity IAP: 2.2.7
Unity IAP 流程/回呼結構
各平台的 IAP 套件經過 Unity 整合至 Purchasing 庫, 開發者只需要實作幾個回呼功能就能輕鬆串接, 不需要花太多時間研究個別的套件.

Purchasing 庫透過初始化時注入實作了 IStoreListner 的實體物件, 將使用者付費的相關事件回呼給 app 本身, 包含初始化結果, 以及付費結果.
實作流程與 IStoreListener 回呼
根據 UnityEngine.Purchasing.dll 描述的 IStoreListener 介面, OnInitialized, OnInitializeFailed, OnPurchaseFailed, ProcessPurchase.
namespace UnityEngine.Purchasing
{
public interface IStoreListener
{
void OnInitialized(IStoreController controller, IExtensionProvider extensions);
void OnInitializeFailed(InitializationFailureReason error);
void OnPurchaseFailed(Product i, PurchaseFailureReason p);
PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e);
}
}
呼叫流程大致如下:

- App 端呼叫 UnityPurchasing.Initialize(IStoreListener listener, ConfigurationBuilder builder).
- App 等待回呼 OnInitialized(IStoreController controller, IExtensionProvider extensions) 或 OnInitializeFailed(InitializationFailureReason error).
- App 使用者觸發購買並呼叫 controller.InitiatePurchase(string productId).
- App 等待購買結果回呼 ProcessPurchase(PurchaseEventArgs e) 或 OnPurchaseFailed(Product i, PurchaseFailureReason p).
- App 確認處理完購買於 ProcessPurchase 回呼中回傳 PurchaseProcessingResult.Complete.
初始化
呼叫 UnityPurchasing.Initialize 開始初始化的流程, 該方法須傳入實作了 IStoreListner 的實體與商店配置 builder. 配置 builder 除了商店配置外, 也要塞入相關的產品資訊.
需要注意的是, 儘管在無網路連接的環境下(包含設備的飛航模式), 初始化也不會失敗, 反而是仍在 app 背景持續嘗試初始化. 因此初始化的時間總是不固定, 在設計上必須防止 app 使用者在為初始化成功的情況下繼續購買流程. 然而, 初始化失敗僅可能由配置錯誤或者該設備不允許 IAP 造成.
以下為官方 Initialize 範例:
public MyIAPManager ()
{
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
builder.AddProduct("100_gold_coins", ProductType.Consumable, new IDs
{
{"100_gold_coins_google", GooglePlay.Name},
{"100_gold_coins_mac", MacAppStore.Name}
});
UnityPurchasing.Initialize (this, builder);
}
配置 builder 除了商店配置外, 也要塞入相關的產品資訊 string id, ProductType type, IDs storeIDs.
| 名稱 | 必要 | 說明 | 範例 |
| id | required | Product ID 平台上的唯一標示符所定義的商品ID, 作為預設ID | “100_gold_coins" |
| type | required | Product Type 必須是以下類型之一 Consumable: 使用者能多次購買. (無法回復) Non-Consumable: 僅提供一次性購買. (能回復) Subscription: 提供特定時限內購買. (能回復) | Subscription更多請參考. |
| IDs | optional | Store ID Overrides 提供跨平台開發定義各平台獨特名稱. 一般情況建議所有平台都使用相同ID, 然某些情況下不允許. 這時能額外定義IDs覆蓋原有名稱. | {“100_gold_coins_google", GooglePlay.Name} |
回呼範例:
/// <summary>
/// Called when Unity IAP is ready to make purchases.
/// </summary>
public void OnInitialized (IStoreController controller, IExtensionProvider extensions)
{
m_Controller = controller;
m_TransactionHistoryExtensions = extensions.GetExtension();
Debug.Log("Available items:");
foreach (var item in controller.products.all)
{
if (item.availableToPurchase)
{
Debug.Log(string.Join(" - ",
new[]
{
item.metadata.localizedTitle,
item.metadata.localizedDescription,
item.metadata.isoCurrencyCode,
item.metadata.localizedPrice.ToString(),
item.metadata.localizedPriceString,
item.transactionID,
item.receipt
}));
}
}
}
/// <summary>
/// Called when Unity IAP encounters an unrecoverable initialization error.
///
/// Note that this will not be called if Internet is unavailable; Unity IAP
/// will attempt initialization until it becomes available.
/// </summary>
public void OnInitializeFailed (InitializationFailureReason error)
{
Debug.Log("Billing failed to initialize!");
switch (error)
{
case InitializationFailureReason.AppNotKnown:
Debug.LogError("Is your App correctly uploaded on the relevant publisher console?");
break;
case InitializationFailureReason.PurchasingUnavailable:
// Ask the user if billing is disabled in device settings.
Debug.Log("Billing disabled!");
break;
case InitializationFailureReason.NoProductsAvailable:
// Developer configuration error; check product metadata.
Debug.Log("No products available for purchase!");
break;
}
}
初始化成功獲得 IStoreController 用於 app 控制 IAP 購買流程. 而 IExtensionProvider 用於存取各平台 IAP 獨特(擴充)的功能, 像是 ITransactionHistoryExtensions 能抓取購買失敗的歷史訊息資料.
初始化失敗從 InitializationFailureReason 查看失敗原因.
購買
當 app 使用者想購買產品時, 呼叫 IStoreController.InitiatePurchase 並指定欲購買的產品ID. app 將被異步通知購買結果, 不是成功 ProcessPurchase 就是失敗 OnPurchaseFailed.
以下為 InitiatePurchase 與回呼範例:
// Example method called when the user presses a 'buy' button
// to start the purchase process.
public void OnPurchaseClicked(string productId)
{
if (m_PurchaseInProgress == true)
{
Debug.Log("Please wait, purchase in progress");
return;
}
if (m_Controller == null)
{
Debug.LogError("Purchasing is not initialized");
return;
}
if (m_Controller.products.WithID(productID) == null)
{
Debug.LogError("No product has id " + productID);
return;
}
m_PurchaseInProgress = true;
m_Controller.InitiatePurchase(productId);
}
/// <summary>
/// Called when a purchase completes.
///
/// May be called at any time after OnInitialized().
/// </summary>
public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
{
Debug.Log("Purchase OK: " + e.purchasedProduct.definition.id);
Debug.Log("Receipt: " + e.purchasedProduct.receipt);
m_PurchaseInProgress = false;
// TODO: distribute content or reward here.
return PurchaseProcessingResult.Complete;
}
/// <summary>
/// Called when a purchase fails.
/// </summary>
public void OnPurchaseFailed (Product i, PurchaseFailureReason p)
{
Debug.Log("Purchase failed: " + i.definition.id);
Debug.Log(p);
m_PurchaseInProgress = false;
}
當購買完成後將回呼 ProcessPurchase 方法, 在這之後 app 必須滿足使用者購買的內容, 並在完成後回傳 PurchaseProcessingResult.Complete.

PS. Unity IAP 也提供非立即的購買完成設計, 更多請參考.
處理失敗
購買失敗的原因很多種, 包括裝置設置, 網路錯誤, 支付失敗或取消. 需要留意並非所有商店都會提供完善的失敗查詢 PurchaseFailureReason, 但我們可以透過 ITransactionHistoryExtensions 盡可能找出詳細的失敗原因.
public void OnPurchaseFailed(Product item, PurchaseFailureReason r)
{
Debug.Log("Purchase failed: " + item.definition.id);
Debug.Log(r);
// Detailed debugging information
Debug.Log("Store specific error code: " + m_TransactionHistoryExtensions.GetLastStoreSpecificPurchaseErrorCode());
if (m_TransactionHistoryExtensions.GetLastPurchaseFailureDescription() != null)
{
Debug.Log("Purchase failure description message: " +
m_TransactionHistoryExtensions.GetLastPurchaseFailureDescription().message);
}
m_PurchaseInProgress = false;
}
通用的 PurchaseFailureReason 僅列舉幾項:
namespace UnityEngine.Purchasing
{
public enum PurchaseFailureReason
{
PurchasingUnavailable = 0,
ExistingPurchasePending = 1,
ProductUnavailable = 2,
SignatureInvalid = 3,
UserCancelled = 4,
PaymentDeclined = 5,
DuplicateTransaction = 6,
Unknown = 7
}
}
參考
Unity IAP package documentation – https://docs.unity3d.com/Packages/com.unity.purchasing@3.1/manual/index.html
Unity IAP Manual – https://docs.unity3d.com/2019.4/Documentation/Manual/UnityIAP.html
在〈Unity 嵌入 app 內購付費 (In-App Purchasing) – 前端實作篇〉中有 1 則留言