应用内购买集成(Swift SDK)
iOS Billing SDK 是实现 Aptoide Connect 计费的简单解决方案。它由一个 Billing 客户端组成,使您能够从 Aptoide Connect 获取产品并处理这些项目的购买。
该 SDK 会自动处理向 Apple 报告交易以用于 Core Technology Commission(CTC)计算,从而免除开发者的这一负担。它包含用于报告购买、退款和其他交易事件的智能逻辑,并采用区分哪些地区需要 CTC 报告、哪些不需要的地区感知处理。
概要
使用 SDK 的应用程序计费流程如下:
- 设置 AppCoins SDK Swift Package;
- 查询您的应用内产品;
- 用户想要购买产品;
- 应用程序发起购买,SDK 进行处理,并在完成时返回购买状态和验证数据;
- 应用程序将产品交付给用户。
分步指南
设置
-
添加 AppCoins SDK Swift Package
在 XCode 中,从仓库 https://github.com/Catappult/appcoins-sdk-ios.git 添加 Swift Package。 -
添加 AppCoins SDK Keychain Access 权限
为了使 AppCoins SDK 能够将用户的 Aptoide Wallet 信息保存在 keychain 中,应用程序需要向 SDK 授予 Keychain Access 权限。为此,请按照以下步骤操作:- 在项目导航器(左侧边栏)中选择您的项目;
- 在“TARGETS”下选择您的 target;
- 转到“Signing & Capabilities”选项卡;
- 点击“+”按钮添加新的 capability;
- 搜索“Keychain Sharing”并选择它;
- 双击以启用“Keychain Sharing”capability;
- 这将自动在“Keychain Groups”文本框中写入您应用的标识符,您应将其替换为“com.aptoide.appcoins-wallet”;
- Xcode 将自动生成一个权限文件(例如 YourAppName.entitlements)并将其添加到您的项目中;
-
添加 AppCoins SDK URL Type
为了管理特定支付方式集成的重定向深度链接,您的应用程序必须在 info.plist 文件中包含一个 URL Type。为此,请按照以下步骤操作:- 在项目导航器(左侧边栏)中选择您的项目。
- 在“TARGETS”下选择您的 target。
- 导航到“Info”选项卡。
- 向下滚动到“URL Types”部分。
- 点击“+”按钮添加新的 URL Type。
- 将 URL Scheme 设置为“$(PRODUCT_BUNDLE_IDENTIFIER).iap”,并将 role 设置为“Editor”。
-
配置数字商品设置
为了启用 SDK 的自动交易报告以用于 CTC(Core Technology Commission)计算,您必须配置您的 target 以表明其销售数字商品。请按照以下步骤操作:- 在项目导航器(左侧边栏)中选择您的项目。
- 在“TARGETS”下选择您的 target。
- 导航到“Info”选项卡。
- 向您的 Target Properties 添加一个新的“MKSellsDigitalGoods”键。
- 将值设置为“YES”以启用数字商品交易报告。
Objective-C 支持
Swift 和 Unity 插件 是推荐的集成方式,但也支持 Objective-C。要在 Objective-C 项目中使用 SDK:
- 从仓库发布页面下载最新的 SDK 版本(
.zip)。 - 将随附的
.xcframework添加到您的项目中。 - 创建一个 bridging header,以将 Swift API 暴露给 Objective-C。
bridging header 就位后,其实现将遵循下文针对 Swift 所述的相同步骤。
实现
现在您已经设置好 SDK 和必要的权限,可以开始使用其功能了。为此,您必须在任何想要使用它的文件中导入 SDK 模块,方法是调用以下语句:import AppCoinsSDK。
-
初始化 AppCoins SDK
⚠️关键: 在使用任何其他 SDK 功能之前,您必须在每个应用程序入口点调用
AppcSDK.initialize()。此方法设置内部 SDK 进程,是 SDK 正常运行所必需的。SDK 必须在应用程序的入口点方法中初始化。根据您应用的设置,这将位于 SceneDelegate.swift(适用于 iOS 13+)或 AppDelegate.swift 中。
SceneDelegate.swift:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
AppcSDK.initialize() // REQUIRED
// ... rest of your code
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
AppcSDK.initialize() // REQUIRED
// ... rest of your code
}AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppcSDK.initialize() // REQUIRED
// ... rest of your code
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
AppcSDK.initialize() // REQUIRED
// ... rest of your code
} -
处理重定向
SDK 需要集成到应用程序的入口点中,以正确处理深度链接。这可确保支付重定向和其他深度链接功能无缝运行。
根据您应用的设置,您应在 SceneDelegate.swift(适用于 iOS 13+)或 AppDelegate.swift(适用于旧版本以及仍在使用它的应用)中处理深度链接。-
SceneDelegate.swift如果您的应用使用 SceneDelegate.swift,请实现以下方法:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
AppcSDK.initialize()
if AppcSDK.handle(redirectURL: URLContexts.first?.url) { return }
// Your application initialization
initialize()
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Create the SwiftUI view that provides the window contents.
let contexts = connectionOptions.urlContexts
AppcSDK.initialize()
// Your application initialization
initialize()
if AppcSDK.handle(redirectURL: contexts.first?.url) { return }
}为何采用此逻辑?
- 在
willConnectTo中首先初始化- 当应用启动或恢复时,必须首先设置 UI 和依赖项。
- 如果 SDK 或服务尚未就绪,在此之前处理深度链接可能会导致问题。
- 在
openURLContexts中优先处理深度链接- 当应用运行时有深度链接到达,立即处理它,如果已处理则 return。
- 这可防止不必要的重新初始化,并确保应用快速响应。
- 在
-
AppDelegate.swift
如果您的应用不使用 SceneDelegate.swift,请在 AppDelegate.swift 中实现深度链接处理。func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppcSDK.initialize()
// Your application initialization
initialize()
if let url = launchOptions?[.url] as? URL {
if AppcSDK.handle(redirectURL: url) { return true }
}
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
AppcSDK.initialize()
if AppcSDK.handle(redirectURL: url) { return true }
// Your application initialization
initialize()
return true
}为何采用此逻辑?
- 在
didFinishLaunchingWithOptions中首先初始化- 在处理深度链接之前,确保 UI 和依赖项已就绪。
- 过早处理深度链接可能会在服务未初始化时导致问题。
- 在
open url中优先处理深度链接- 当应用运行时收到深度链接,立即处理它。
- 如果
AppcSDK.handle(redirectURL:)处理了该链接,则提前 return。
- 在
-
-
检查 AppCoins SDK 可用性(支付分支)
默认情况下,AppCoins SDK 仅在 iOS 版本等于或高于 17.4 的设备上可用,且仅当应用程序不是通过 Apple App Store 安装时才可用。因此,在尝试购买之前,您应通过调用AppcSDK.isAvailable来检查 SDK 是否可用。isAvailable()仅在两个条件都满足时返回true:应用是从替代应用市场安装的,并且设备运行 iOS 17.4 或更高版本。对于通过 App Store 安装以及低于 iOS 17.4 的设备,它会返回false。请使用它来分支您的计费逻辑:当它返回true时,使用 AppCoins SDK;否则,回退到 Apple In-App Purchase。if await AppcSDK.isAvailable() {
// 在 iOS 17.4+ 上从替代应用市场安装:使用 AppCoins SDK
} else {
// 通过 App Store 安装或 iOS 低于 17.4:使用 Apple In-App Purchase
} -
查询应用内产品
您应首先获取要向用户提供的应用内产品。您可以通过调用Product.products来执行此操作。此方法既可以返回您所有的 Aptoide 应用内产品,也可以返回特定列表。
-
Product.products()返回应用程序所有的 Aptoide 应用内产品:
let products = try await Product.products() -
Product.products(for: [String])返回 Aptoide 应用内产品的特定列表:
let products = try await Product.products(for: ["gas"])⚠️警告: 只有在您的应用程序在 Aptoide Connect 上经过审核并获得批准后,您才能查询您的应用内产品。
-
-
购买应用内产品
要购买应用内产品,您必须在 Product 对象上调用purchase()函数。SDK 将为您处理所有购买逻辑,并在完成时返回购买结果。此结果可以是.success(let verificationResult)、.pending、.userCancelled或.failed(let error)。在成功的情况下,应用程序将在本地验证交易的签名。验证后,您应处理其结果:
– 如果购买已验证,您应消耗该项目并将其交付给用户;
– 如果未验证,您需要根据您的业务逻辑做出决定,您可以仍然消耗该项目并将其交付给用户,否则该购买将不会被确认,我们将在 24 小时内向用户退款。在失败的情况下,您可以在 switch 语句中处理不同类型的错误。SDK 返回的每个错误都是
AppCoinsSDKError类型,本文档后面将对其进行说明。您还可以向 purchase 方法传递一个 Payload,以便将某种信息与特定购买关联。例如,您可以使用它将特定用户与某次购买关联起来:
gas.purchase(payload: "User123")。\let result = await products?.first?.purchase()
switch result {
case .success(let verificationResult):
switch verificationResult {
case .verified(let purchase):
// consume the item and give it to the user
try await purchase.finish()
case .unverified(let purchase, let verificationError):
// deal with unverified transactions
}
case .pending: // transaction is not finished
case .userCancelled: // user cancelled the transaction
case .failed(let error): // deal with any possible errors
} -
在应用启动时处理未完成的购买(关键)
⚠️关键: 每次应用程序启动时,您都必须查询并消耗未完成的购买。否则会导致用户无法收到他们已经付费的项目,并且未在 24 小时内消耗的购买将被自动退款。
未完成的购买是指已付费但尚未被您的应用程序消耗的交易。这可能在以下情况下发生:
- 应用在购买过程中被关闭或崩溃
- 用户在购买被处理之前强制退出应用
- 购买完成期间发生网络错误
为何这至关重要:
- 用户已经为这些项目付费
- 如果未在 24 小时内消耗,购买将被自动退款
- 用户期望在重新打开应用后立即收到他们购买的项目
最佳实践:在应用的初始化流程中调用
Purchase.unfinished(),最好在检查 SDK 可用性之后。// Example: In your app initialization (e.g., ViewModel or app startup)
func initializeApp() async {
if await AppcSDK.isAvailable() {
do {
// Query all unfinished purchases
let unfinishedPurchases = try await Purchase.unfinished()
// Consume each purchase and give the user their items
for purchase in unfinishedPurchases {
// Give the item to the user based on the SKU
giveItemToUser(sku: purchase.sku)
// Mark the purchase as finished
try await purchase.finish()
}
} catch {
// Handle error - log it and potentially retry later
print("Failed to process unfinished purchases: \(error)")
}
}
} -
处理购买意图
除标准的应用内购买外,AppCoins SDK 还支持应用内购买意图——并非由用户操作直接触发的购买(例如,在应用内点击“购买”按钮)。常见用例包括:
- 直接从 Aptoide Store 的应用内产品目录中购买项目。
- 通过网页链接购买项目。
购买意图可以通过以下 URL 格式发起:
{domain}.iap://wallet.appcoins.io/purchase?product={sku}&oemid={oemid}&discount_policy={discount_policy}domain– 您应用程序的 Bundle ID。oemid– 与您在 Aptoide Connect 上的开发者账户关联的 OEM ID。discount_policy– 要应用的折扣策略(例如 D2C)。
SDK 允许开发者通过
Purchase.updates方法管理这些购买并向用户交付消耗型商品。此方法返回一个Task对象,用于流式传输实时购买更新,从而实现无缝的交易处理。该流会发出一个
PurchaseIntent对象,您可以根据您的应用程序逻辑对其进行管理。PurchaseIntent类提供两个方法:confirm(payload: String?, orderID: String?):确认并处理购买。等同于调用.purchase()。reject():拒绝该意图,使其在将来无法使用。
如果您不希望立即处理该意图——例如,等待用户登录以便将购买与其账户关联——您可以先忽略该意图。之后,当您的逻辑允许时,您可以调用
Purchase.intent,它会返回当前待处理的意图。然后您可以根据需要确认或拒绝它。以下是处理应用内购买意图的骨架实现。\
import AppCoinsSDK
actor PurchaseManager {
static let shared = PurchaseManager() // Singleton instance
private init() {
Task { await observePurchases() }
}
private func observePurchases() async {
for await intent in Purchase.updates {
if User.isSignedIn {
let result = await intent.confirm()
await handle(purchaseResult: result)
}
}
}
// HINT: You can use the same handle method for both regular and intent IAP
private func handle(result: PurchaseResult) async {
switch result {
case .success(let verificationResult):
switch verificationResult {
case .verified(let purchase):
// consume the item and give it to the user
try await purchase.finish()
case .unverified(let purchase, let verificationError):
// deal with unverified transactions
}
case .pending: // transaction is not finished
case .userCancelled: // user cancelled the transaction
case .failed(let error): // deal with any possible errors
}
}
}
-
查询购买
您可以使用以下方法之一来查询用户的购买:-
Purchase.all此方法返回用户在您的应用程序中执行的所有购买。
let purchases = try await Purchase.all() -
Purchase.latest(sku: String)此方法返回特定应用内产品的用户最新购买。
let purchase = try await Purchase.latest(sku: "gas") -
Purchase.unfinished此方法返回用户在应用程序中所有未完成的购买。未完成的购买是指既未被确认(由 SDK 验证)也未被消耗的任何购买。
⚠️关键: 您必须在应用初始化期间调用此方法,以确保用户能收到因购买被中断而获得的项目。有关详细实现,请参阅步骤 6“在应用启动时处理未完成的购买”。
let purchases = try await Purchase.unfinished()
-
测试
以下 Xcode 设置仅用于在开发期间模拟安装来源。在生产环境中,SDK 会通过 Apple 的 API 自动检测真实的安装来源,这些测试设置将被忽略。它们对生产构建没有任何影响。
要在开发期间测试 SDK 集成,您需要为开发构建设置安装来源,模拟应用是通过 Aptoide 分发的。此操作将启用 SDK 的 isAvailable 方法。
请按照以下步骤操作:
-
在您的 target 构建设置中,搜索“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。
沙盒
为了验证您的计费集成是否设置成功,我们提供了一个沙盒环境,您可以在其中模拟购买并确保您的客户能够顺利购买您的产品。有关如何使用此环境的文档可在以下位置找到:沙盒
类定义和属性
SDK 集成基于处理其逻辑的四个主要对象类:
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 - 购买意图的唯一标识符。timestamp: Date - 意图创建的日期和时间。product: Product - 用户打算购买的产品。
AppcSDK
此类负责处理通用方法,例如处理重定向或检查 SDK 是否可用。
方法:
initialize(): 必需 - 设置内部 SDK 进程。必须在每个应用程序入口点(SceneDelegate 或 AppDelegate 方法)中调用。如果没有此调用,SDK 将无法正常运行。isAvailable() async -> Bool: 检查 AppCoins SDK 在当前设备上是否可用。如果设备运行 iOS 17.4+,且应用不是通过 Apple App Store 安装的,则返回 true。handle(redirectURL: URL?) -> Bool: 处理支付重定向和购买意图的深度链接。如果 SDK 处理了该 URL,则返回 true。
AppCoinsSDKError
SDK 在执行任何操作时可能返回的错误枚举。
可能的错误:
networkError: 与网络相关的错误;systemError: 内部 APPC 系统错误;notEntitled: 宿主应用未配置正确的权限;productUnavailable: 产品不可用;purchaseNotAllowed: 不允许用户执行该购买;unknown: 其他错误。
常见问题
如果我已经使用 StoreKit,还需要 Aptoide Billing SDK 吗?
简短回答: 需要。Apple In-App Purchase 在替代应用市场中不可用。
详细说明: 要在通过替代应用市场分发的应用中提供应用内购买,集成 Aptoide iOS Billing SDK(AppCoins SDK)是强制性的。当使用替代计费系统时,您还需要向 Apple 报告外部购买;请在适用的地区完成相应的外部应用内购买报告配置。
每个平台上有哪些 SDK 功能可用?
简短回答: iOS 提供应用内购买;Android 还额外提供 App Updates 和 Referral Deeplinks。
详细说明:
- iOS: 仅应用内购买。
- Android: 应用内购买、App Updates 和 Referral Deeplinks。
两个平台均不提供应用内评价弹窗(没有与 Google Play In-App Review API 相当的功能)。