应用内购买集成(Unity 插件)
iOS Billing SDK 是实现 Aptoide 计费的简单解决方案。其 Unity 插件为 Unity 游戏提供了一个简单的接口,用于与 SDK 通信。它由一个 Billing 客户端组成,可让您从 Aptoide Connect 获取商品并处理这些商品的购买。
该 SDK 会自动处理向 Apple 上报交易以计算 Core Technology Commission(CTC)的工作,从而免除开发者的这一负担。它包含用于上报购买、退款及其他交易事件的智能逻辑,并具备区域感知处理能力,可区分哪些区域需要 CTC 上报、哪些不需要。
概述
使用该插件后,您应用中的计费流程如下:
- 添加 AppCoins Unity 插件;
- 查询您的应用内商品;
- 用户想要购买某个商品;
- 应用发起购买,由插件进行处理,并在完成时返回购买状态和验证数据;
- 应用将商品交付给用户。
要求
- Unity 2019.4 或更高版本。
- iOS 17.4 或更高版本。
分步指南
设置
- 添加 AppCoins Unity 插件
在 Unity 中,将仓库 https://github.com/Catappult/appcoins-sdk-ios-unity-plugin 中提供的最新版本插件添加到您的 Assets 文件夹中。
命名空间
插件中的所有公共类都定义在 AppCoins 命名空间下。请在任何引用插件类型的 C# 文件顶部添加以下 using 指令:
using AppCoins;
或者,您也可以使用完全限定名称(例如 AppCoins.Product、AppCoins.Purchase)。
实现
现在您已完成插件的设置,可以开始使用其各项功能。
-
检查 AppCoins Billing 的可用性
AppCoins Billing 仅在 iOS 版本等于或高于 17.4 的设备上可用,并且仅当应用不是通过 Apple App Store 安装时才可用。因此,在尝试任何购买之前,您应通过调用
AppCoinsSDK.Instance.IsAvailable()来检查 SDK 是否可用。using AppCoins;
var isAvailable = await AppCoinsSDK.Instance.IsAvailable();
if (isAvailable)
{
// make purchase
} -
查询应用内商品
您应首先获取要向用户提供的应用内商品。此方法既可以返回您所有的 Catappult 应用内商品,也可以返回特定的列表。
-
AppCoinsSDK.Instance.GetProducts()返回应用的所有 Catappult 应用内商品:
using AppCoins;
var productsResult = await AppCoinsSDK.Instance.GetProducts();
if (productsResult.IsSuccess)
{
var products = productsResult.Value;
// Process products
}
else
{
Debug.Log("Error: " + productsResult.Error);
} -
AppCoinsSDK.Instance.GetProducts(skus)返回特定的 Catappult 应用内商品列表:
using AppCoins;
var productsResult = await AppCoinsSDK.Instance.GetProducts(new string[] { "coins_100", "gas" });
if (productsResult.IsSuccess)
{
var products = productsResult.Value;
// Process products
}
else
{
Debug.Log("Error: " + productsResult.Error);
}⚠️警告: 只有在您的应用经 Aptoide Connect 审核并批准后,您才能查询您的应用内商品。
-
-
购买应用内商品
要购买应用内商品,您必须调用函数
AppCoinsSDK.Instance.Purchase(sku, payload)。插件将为您处理所有购买逻辑,并在完成时返回购买结果。该结果是一个具有以下属性的AppCoinsSDKPurchaseResult对象:State:String - 购买状态(AppCoinsSDK.PURCHASE_STATE_SUCCESS、AppCoinsSDK.PURCHASE_STATE_PENDING、AppCoinsSDK.PURCHASE_STATE_USER_CANCELLED、AppCoinsSDK.PURCHASE_STATE_FAILED)Value:包含以下内容的对象:VerificationResult:String - 验证结果(AppCoinsSDK.PURCHASE_VERIFICATION_STATE_VERIFIED、AppCoinsSDK.PURCHASE_VERIFICATION_STATE_UNVERIFIED)Purchase:Purchase 对象VerificationError:AppCoinsSDKError(仅在验证失败时存在)
Error:AppCoinsSDKError - 错误详情(仅当状态为FAILED时存在)
购买成功时,应用将在本地验证交易的签名。完成此验证后,您应处理其结果:
- 如果购买已通过验证,您应消费该商品并将其交付给用户。
- 如果未通过验证,您需要根据自己的业务逻辑做出决定:要么仍然消费该商品并将其交付给用户,要么不确认该购买,我们将在 24 小时内向用户退款。
购买失败时,您可以处理不同类型的错误。
您还可以向购买方法传递 Payload,以便将某种信息与特定购买关联起来。例如,您可以使用它将特定用户与某次购买关联:
AppCoinsSDK.Instance.Purchase("gas", "User123")。
using AppCoins;
var purchaseResult = await AppCoinsSDK.Instance.Purchase("gas", "User123");
switch (purchaseResult.State)
{
case AppCoinsSDK.PURCHASE_STATE_SUCCESS:
switch (purchaseResult.Value.VerificationResult)
{
case AppCoinsSDK.PURCHASE_VERIFICATION_STATE_VERIFIED:
// Consume the item and give it to the user
var consumeResult = await AppCoinsSDK.Instance.ConsumePurchase(purchaseResult.Value.Purchase.Sku);
if (consumeResult.IsSuccess)
{
Debug.Log("Purchase consumed successfully");
}
else
{
Debug.Log("Error consuming purchase: " + consumeResult.Error);
}
break;
case AppCoinsSDK.PURCHASE_VERIFICATION_STATE_UNVERIFIED:
// Handle unverified purchase according to your game logic
break;
}
break;
case AppCoinsSDK.PURCHASE_STATE_PENDING:
// Handle pending purchase according to your game logic
Debug.Log("Purchase is pending.");
break;
case AppCoinsSDK.PURCHASE_STATE_USER_CANCELLED:
// Handle cancelled purchase according to your game logic
Debug.Log("Purchase was cancelled.");
break;
case AppCoinsSDK.PURCHASE_STATE_FAILED:
// Handle failed purchase according to your game logic
Debug.Log("Purchase failed with error: " + purchaseResult.Error);
break;
} -
在应用启动时处理未完成的购买(关键)
⚠️关键: 每次应用启动时,您必须查询并消费未完成的购买。否则将导致用户无法收到他们已付费的商品,并且未消费的购买将在 24 小时后被自动退款。
什么是未完成的购买?
未完成的购买是指已付费但尚未被您的应用消费的交易。这种情况可能在以下情形下发生:
- 购买过程中应用被关闭或崩溃
- 用户在购买被处理之前强制退出了应用
- 购买完成期间发生了网络错误
为何这一点至关重要:
- 用户已经为这些商品付费
- 如果未在 24 小时内消费,购买将被自动退款
- 用户期望在重新打开应用时立即收到所购买的商品
实现:
将以下代码添加到应用的启动逻辑中(例如主场景的
Start()或Awake()方法中):using AppCoins;
private async void Start()
{
// Check if AppCoins Billing is available
var isAvailable = await AppCoinsSDK.Instance.IsAvailable();
if (!isAvailable)
{
return;
}
// Query and consume unfinished purchases
var unfinishedPurchasesResult = await AppCoinsSDK.Instance.GetUnfinishedPurchases();
if (unfinishedPurchasesResult.IsSuccess)
{
var purchases = unfinishedPurchasesResult.Value;
foreach (var purchase in purchases)
{
// Give the item to the user
GiveItemToUser(purchase.Sku);
// Consume the purchase
var consumeResult = await AppCoinsSDK.Instance.ConsumePurchase(purchase.Sku);
if (consumeResult.IsSuccess)
{
Debug.Log($"Unfinished purchase consumed successfully: {purchase.Sku}");
}
else
{
Debug.Log($"Error consuming purchase: {consumeResult.Error}");
}
}
}
else
{
Debug.Log("Error querying unfinished purchases: " + unfinishedPurchasesResult.Error);
}
}
private void GiveItemToUser(string sku)
{
// Your logic to grant the purchased item to the user
Debug.Log($"Giving item to user: {sku}");
} -
处理间接购买
除了标准的应用内购买外,AppCoins SDK 还支持应用内购买意图——即并非由用户操作直接触发的购买(例如点击应用内的"购买"按钮)。常见的用例包括:
- 直接从 Aptoide 商店的应用内商品目录中购买商品。
- 通过网页链接购买商品。
购买意图可通过以下 URL 格式发起:
AppCoinsPurchaseManager.OnPurchaseUpdatedUnity Action 允许开发者管理这些购买意图。该事件会持续推送购买意图的更新,确保交易实时同步。该事件返回一个
PurchaseIntent对象,其中包含:ID:String - 意图的唯一标识符Product:Product - 用户想要购买的商品Timestamp:String - 意图的创建时间
当您收到
PurchaseIntent时,您必须执行以下操作之一:使用AppCoinsSDK.Instance.ConfirmPurchaseIntent(payload)确认该意图以完成购买,或使用AppCoinsSDK.Instance.RejectPurchaseIntent()拒绝该意图以取消。确认意图会返回一个AppCoinsSDKPurchaseResult,其处理方式应与标准购买相同。为正确处理购买意图,请在单例类中订阅该事件,确保它在应用的整个生命周期内保持活跃。
注意: 您也可以使用
AppCoinsSDK.Instance.GetPurchaseIntent()手动检查待处理的购买意图。当用户登录或您的应用变为活跃状态时,这有助于确保不会遗漏任何待处理的意图。
using AppCoins;
private void Awake()
{
// Singleton enforcement
if (Instance != null && Instance != this)
{
Destroy(gameObject); // Destroy duplicate instances
return;
}
Instance = this;
DontDestroyOnLoad(gameObject); // Persist across scenes
// Subscribe to purchase intent updates
AppCoinsPurchaseManager.OnPurchaseUpdated += HandlePurchaseIntent;
}
private async void HandlePurchaseIntent(PurchaseIntent purchaseIntent)
{
Debug.Log($"Received purchase intent for: {purchaseIntent.Product.Title}");
// Confirm the purchase intent to complete the transaction
var purchaseResult = await AppCoinsSDK.Instance.ConfirmPurchaseIntent("User123");
// Handle the purchase result the same way as a standard purchase
switch (purchaseResult.State)
{
case AppCoinsSDK.PURCHASE_STATE_SUCCESS:
switch (purchaseResult.Value.VerificationResult)
{
case AppCoinsSDK.PURCHASE_VERIFICATION_STATE_VERIFIED:
var consumeResult = await AppCoinsSDK.Instance.ConsumePurchase(purchaseResult.Value.Purchase.Sku);
if (consumeResult.IsSuccess)
{
Debug.Log("Purchase consumed successfully");
}
else
{
Debug.Log("Error consuming purchase: " + consumeResult.Error);
}
break;
case AppCoinsSDK.PURCHASE_VERIFICATION_STATE_UNVERIFIED:
// Handle unverified purchase according to your game logic
break;
}
break;
case AppCoinsSDK.PURCHASE_STATE_USER_CANCELLED:
Debug.Log("Purchase was cancelled.");
break;
case AppCoinsSDK.PURCHASE_STATE_FAILED:
Debug.Log("Purchase failed with error: " + purchaseResult.Error);
break;
}
// Alternatively, reject the purchase intent to cancel:
// AppCoinsSDK.Instance.RejectPurchaseIntent();
} -
查询购买记录
您可以使用以下方法之一查询用户的购买记录:
-
AppCoinsSDK.Instance.GetAllPurchases()此方法返回用户在您的应用中执行的所有购买。
using AppCoins;
var purchasesResult = await AppCoinsSDK.Instance.GetAllPurchases();
if (purchasesResult.IsSuccess)
{
var purchases = purchasesResult.Value;
// Process purchases
}
else
{
Debug.Log("Error: " + purchasesResult.Error);
} -
AppCoinsSDK.Instance.GetLatestPurchase(string sku)此方法返回用户针对特定应用内商品的最近一次购买。如果未找到购买记录,则返回
null。using AppCoins;
var latestPurchaseResult = await AppCoinsSDK.Instance.GetLatestPurchase("gas");
if (latestPurchaseResult.IsSuccess)
{
if (latestPurchaseResult.Value != null)
{
var purchase = latestPurchaseResult.Value;
// Process purchase
}
else
{
Debug.Log("No latest purchase found for this SKU");
}
}
else
{
Debug.Log("Error: " + latestPurchaseResult.Error);
} -
AppCoinsSDK.Instance.GetUnfinishedPurchases()此方法返回用户在应用中所有未完成的购买。未完成的购买是指既未被确认(即被 SDK 验证)也未被消费的任何购买。您可以使用此方法来消费任何未完成的购买。
using AppCoins;
var unfinishedPurchasesResult = await AppCoinsSDK.Instance.GetUnfinishedPurchases();
if (unfinishedPurchasesResult.IsSuccess)
{
var purchases = unfinishedPurchasesResult.Value;
foreach (var purchase in purchases)
{
var consumeResult = await AppCoinsSDK.Instance.ConsumePurchase(purchase.Sku);
if (consumeResult.IsSuccess)
{
Debug.Log("Unfinished purchase consumed successfully");
}
else
{
Debug.Log("Error consuming purchase: " + consumeResult.Error);
}
}
}
else
{
Debug.Log("Error: " + unfinishedPurchasesResult.Error);
}
-
测试
以下 Xcode 设置仅用于在开发期间模拟安装来源。在生产环境中,SDK 会通过 Apple 的 API 自动检测真实的安装来源,这些测试设置将被忽略。它们对生产版本没有任何影响。
要在开发期间测试 SDK 集成,您需要为开发版本设置安装来源,以模拟应用通过 Aptoide 分发的情形。此操作将启用 SDK 的 isAvailable 方法。
请在 Xcode 中按照以下步骤操作:
-
在目标的构建设置中,搜索 "Marketplaces"。
-
在 "Deployment" 下,将键 "Marketplaces" 或 "Alternative Distribution - Marketplaces" 设置为 "com.aptoide.ios.store"。

-
在您的 scheme 中,转到 "Run" 选项卡,然后导航到 "Options" 选项卡。在 "Distribution" 下拉菜单中,选择 "com.aptoide.ios.store"。

有关更多信息,请参阅 Apple 的官方文档:https://developer.apple.com/documentation/appdistribution/distributing-your-app-on-an-alternative-marketplace#Test-your-app-during-development
在单个版本中测试两种计费系统
为便于在单个版本中同时测试 Apple Billing 和 Aptoide Billing——而无需生成应用的多个独立版本——AppCoins SDK 包含了一种深度链接机制,可在 true 和 false 之间切换 SDK 的 isAvailable 方法。这使您能够在测试 AppCoins SDK(可用时)和 Apple Billing(不可用时)之间无缝切换。
要启用或禁用 AppCoins SDK,请打开设备的浏览器并输入以下 URL:
{domain}.iap://wallet.appcoins.io/default?value={value}
其中:
domain– 您应用的 Bundle ID。valuetrue→ 启用 AppCoins SDK 以进行测试。false→ 禁用 AppCoins SDK,从而改为测试 Apple Billing。
沙盒
为验证您的计费集成是否设置成功,我们提供了一个沙盒环境,您可以在其中模拟购买,确保您的客户能够顺畅地购买您的商品。有关如何使用此环境的文档,请参阅:沙盒
类定义与属性
Unity 插件集成基于若干处理其逻辑的主要对象类:
Product
Product 表示一个应用内商品。
属性:
Sku:String - 唯一的商品标识符。示例:gasTitle:String - 商品的显示标题。示例:Best GasDescription:String - 商品描述。示例:Buy gas to fill the tank.PriceCurrency:String - 用户的地理定位货币。示例:EURPriceValue:String - 商品以指定货币计的价值。示例:0.93PriceLabel:String - 向用户显示的价格标签。示例:€0.93PriceSymbol:String - 地理定位货币的符号。示例:€
Purchase
Purchase 表示一次应用内购买。
属性:
UID:String - 唯一的购买标识符。示例:catappult.inapp.purchase.ABCDEFGHIJ1234Sku:String - 所购买商品的唯一标识符。示例:gasState:String - 购买状态可以是三者之一:PENDING、ACKNOWLEDGED 和 CONSUMED。Pending 购买是既未经 SDK 验证、也未被应用消费的购买。Acknowledged 购买是已经过 SDK 验证、但尚未被消费的购买。示例:CONSUMEDOrderUID:String - 与购买关联的 orderUid。示例:ZWYXGYZCPWHZDZUK4HPayload:String - 开发者 Payload。示例:707048467.998992Created:String - 购买的创建日期。示例:2023-01-01T10:21:29.014456ZVerification:PurchaseVerification - 与购买关联的验证数据。
PurchaseVerification
PurchaseVerification 表示应用内购买的验证数据。
属性:
Type:String - 所进行的验证类型。示例:GOOGLESignature:String - 购买签名。示例:C4x6cr0HJk0KkRqJXUrRAhdANespHEsyx6ajRjbG5G/v3uBzlthkUe8BO7NXH/1Yi/UhS5sk7huA+hB8EbaQK9bwaiV/Z3dISl5jgYqzSEz1c/PFPwVEHZTMrdU07i/q4FD33x0LZIxrv2XYbAcyNVRY3GLJpgzAB8NvKtumbWrbV6XG4gBmYl9w4oUgJLnedii02beKlvmR7suQcqIqlSKA9WEH2s7sCxB5+kYwjQ5oHttmOQENnJXlFRBQrhW89bl18rccF05ur71wNOU6KgMcwppUccvIfXUpDFKhXQs4Ut6c492/GX1+KzbhotDmxSLQb6aw6/l/kzaSxNyjHg==Data:PurchaseVerificationData - 与购买验证关联的数据。
PurchaseVerificationData
PurchaseVerificationData 表示应用内购买验证数据的主体。
属性:
OrderId:String - 与购买关联的 orderUid。示例:372EXWQFTVMKS6HIPackageName:String - 商品所属应用的 Bundle ID。示例:com.appcoins.trivialdrivesampleProductId:String - 所购买商品的唯一标识符。示例:gasPurchaseTime:Integer - 商品的购买时间。示例:1583058465823PurchaseToken:String - 购买商品时提供给用户设备的令牌。示例:catappult.inapp.purchase.SZYJ5ZRWUATW5YU2PurchaseState:Integer - 订单的购买状态。可能的值为:0(已购买)和 1(已取消)DeveloperPayload:String - 由开发者指定的字符串,包含有关订单的补充信息。示例:myOrderId:12345678
PurchaseIntent
PurchaseIntent 表示一个间接的应用内购买意图。
属性:
ID:String - 意图的唯一标识符。示例:550e8400-e29b-41d4-a716-446655440000Product:Product - 用户想要购买的商品Timestamp:String - 意图的创建时间。示例:2025-01-15T10:21:29.014456Z
AppCoinsSDKPurchaseResult
AppCoinsSDKPurchaseResult 表示一次购买操作的结果。
属性:
State:String - 购买状态。可以是:AppCoinsSDK.PURCHASE_STATE_SUCCESS- 购买成功完成AppCoinsSDK.PURCHASE_STATE_PENDING- 购买待处理AppCoinsSDK.PURCHASE_STATE_USER_CANCELLED- 用户取消了购买AppCoinsSDK.PURCHASE_STATE_FAILED- 购买失败
Value:Object(仅当 State 为 SUCCESS 时存在),包含:VerificationResult:String - 可以是AppCoinsSDK.PURCHASE_VERIFICATION_STATE_VERIFIED或AppCoinsSDK.PURCHASE_VERIFICATION_STATE_UNVERIFIEDPurchase:Purchase - 购买对象VerificationError:AppCoinsSDKError(可选) - 验证失败时的错误详情
Error:AppCoinsSDKError(仅当 State 为 FAILED 时存在) - 错误详情
AppCoinsSDKResult<T>
AppCoinsSDKResult<T> 表示返回数据的 SDK 操作的结果。
属性:
IsSuccess:Boolean - 操作是否成功Value:T - 结果值(仅当 IsSuccess 为 true 时存在)Error:AppCoinsSDKError(仅当 IsSuccess 为 false 时存在) - 错误详情
使用方:
GetProducts()- 返回AppCoinsSDKResult<Product[]>GetAllPurchases()- 返回AppCoinsSDKResult<Purchase[]>GetLatestPurchase(sku)- 返回AppCoinsSDKResult<Purchase>GetUnfinishedPurchases()- 返回AppCoinsSDKResult<Purchase[]>ConsumePurchase(sku)- 返回AppCoinsSDKResult<bool>GetTestingWalletAddress()- 返回AppCoinsSDKResult<string>GetPurchaseIntent()- 返回AppCoinsSDKResult<PurchaseIntent>
AppCoinsSDKError
AppCoinsSDKError 表示 SDK 操作失败时的错误信息。
属性:
Type:String - 错误类型。可以是:networkError- 网络相关的错误systemError- 系统或 SDK 错误notEntitled- 用户无权获得该商品productUnavailable- 商品不可用purchaseNotAllowed- 不允许购买unknown- 未知错误
Message:String - 简短的错误消息Description:String - 详细的错误描述Request:ErrorRequest(可选) - 请求详情(如果可用)
ErrorRequest
属性:
URL:String - 请求 URLMethod:String - HTTP 方法Body:String - 请求主体ResponseData:String - 响应数据StatusCode:Integer - HTTP 状态码
PurchaseIntent 表示用户进行应用内购买的意图。它通常用于确认或拒绝在应用外发起的购买。
此类负责通用方法,并通过 AppCoinsSDK.Instance 提供单例访问。