本指南讨论如何在 Solar2D 应用中实现
应用内购买 (IAP) 允许用户从应用内购买额外内容。但是,此内容不能像实体库存一样通过市场交付 — 您必须在构建应用时将内容与其捆绑在一起,预期它会在购买后解锁/启用,或者您必须使用 网络 API 将其他内容下载到应用中。
应用内
每个商店的命名方式都不同。请参考下表
产品类型 | Apple | Amazon | |
---|---|---|---|
一次性购买 | 非消耗型 | 托管商品 | 权限 |
多次购买 | 消耗型 | 带消耗的托管商品 | 消耗型 |
自动续订订阅 | 自动续订订阅 | 订阅 | 订阅 |
非续订订阅 | 非续订订阅 |
产品由称为**产品标识符**的唯一字符串标识。每个商店可能使用不同的术语,但在所有情况下它都是一个字符串值,对于您的应用必须是唯一的。建议您在所有商店中遵循
com.acmegames.SuperRunner.UnlockFullGame
在商店门户中配置产品时,产品标识符应命名清晰准确,因为您需要在 Solar2D 代码中使用它们来加载产品、提交购买请求并在交易完成后识别商品。此外,如果您打算支持多个商店/平台,则应在所有平台上为每个产品使用一致的名称 — 这将避免在整个 IAP 实现过程中需要额外的条件代码。
每个市场——Apple App Store、Google Play 和 Amazon Appstore——都有不同的
以下步骤适用于在 **iOS** 上进行
首先在 iTunes Connect 中配置您的银行和税务信息。
在 Apple Developer 门户中,创建一个唯一且完全限定的新 App ID
返回 iTunes Connect,使用与 Apple Developer 门户中的应用相同的**Bundle ID** 创建一个新应用。
最后,在 iTunes Connect 中配置您的
以下步骤适用于在 **Android** 上进行应用内购买。
首先设置您的 Google 商家帐户并将其链接到 Google Play 开发者控制台。
仍在控制台中时,创建一个新应用并填写发布应用所需的所有信息 — 但请注意,某些详细信息无需最终确定即可简单测试
配置您的
在 Solar2D 方面,通过在项目的 `build.settings` 文件的 `plugins` 表中添加条目来集成 Google Billing 插件
settings = { plugins = { ["plugin.google.iap.billing"] = { publisherId = "com.coronalabs" }, }, }
application = { license = { google = { key = "YOUR_KEY", }, }, }
以下步骤适用于在 **Amazon** 上进行应用内购买。
如果您还没有,请注册一个 Amazon Developer 帐户。
如果您不熟悉
配置您的
在 Solar2D 方面,通过在项目的 `build.settings` 文件的 `plugins` 表中添加条目来集成 Amazon IAP 插件
settings = { plugins = { ["plugin.amazon.iap"] = { publisherId = "com.coronalabs" }, }, }
由于每个 IAP 提供商在 Solar2D 端使用不同的模块/插件,因此您必须加载正确的模块/插件。
如果您只支持一个商店,您可以简单地使用 `require()` 加载相应的模块,如下所示
local store = require( "store" ) -- iOS
local store = require( "plugin.google.iap.billing" ) -- Android
local store = require( "plugin.amazon.iap" ) -- Amazon
或者,如果您要支持多个平台,则可以实现条件例程。在以下示例中,使用 system.getInfo() 调用来检测构建的应用将面向哪个商店,并使用该属性来加载相应的模块。
local store local targetAppStore = system.getInfo( "targetAppStore" ) if ( "apple" == targetAppStore ) then -- iOS store = require( "store" ) elseif ( "google" == targetAppStore ) then -- Android store = require( "plugin.google.iap.billing" ) elseif ( "amazon" == targetAppStore ) then -- Amazon store = require( "plugin.amazon.iap" ) else print( "In-app purchases are not available for this platform." ) end
在为
加载正确的模块后,您必须使用 `store.init()` 调用对其进行初始化
store.init( transactionListener )
唯一必需的参数 `transactionListener` 是您打算用于处理商店**交易**请求的函数,包括购买和潜在退款。例如
local function transactionListener( event ) local transaction = event.transaction if ( transaction.isError ) then print( transaction.errorType ) print( transaction.errorString ) else -- No errors; proceed end end -- Initialize store store.init( transactionListener )
`store.init()` 调用是必需的,并且必须在您尝试调用任何其他
对于 Google IAP,此相同的交易侦听器函数还处理初始化(init)事件。因此,如果您使用的是 Google IAP,则应通过有条件地检查 event.name 的值来区分商店交易事件和初始化事件。有关执行此操作的示例,请参阅 Google IAP store.init() 文档。
调用 `store.init()` 后,您可以检查 `store.isActive` 属性以确认商店已成功初始化(值为 `true`)。
在您的 Solar2D 应用中,您可以使用 `store.loadProducts()` 函数加载您在相应商店中输入的产品信息
store.loadProducts( productIdentifiers, productListener )
此函数需要以下两个参数
local productIdentifiers = { "com.domainname.testProduct1", "com.domainname.testProduct2", "com.domainname.testProduct3", }
local productIdentifiers = { "com.domainname.testProduct1", "com.domainname.testProduct2", "com.domainname.testProduct3", } local function productListener( event ) end -- Load store products; store must be properly initialized by this point! store.loadProducts( productIdentifiers, productListener )
如果您为所有平台保持产品标识符一致,则可以使用一个产品数组,但如果您的标识符因商店而异,则需要使用单独的数组和条件逻辑与`system.getInfo("targetAppStore")`
加载产品后,产品侦听器函数(`store.loadProducts()` 的 `productListener` 参数)将接收一个 `event` 表作为其唯一参数。由于商店功能的核心差异,与此表关联的属性/键会略有不同,但至少会有以下两个属性可用
`event.products` — 一个表,其中每个元素是另一个包含详细产品信息的表。这主要是您需要检查以获取有关已加载产品的详细信息的表。
`event.invalidProducts` — 一个表,其中每个元素都是一个表示产品标识符的字符串。此表将仅由不存在或不可用的产品填充。
通常,您应该遍历(循环)`event.products` 以确定哪些产品可用。例如
local productIdentifiers = { "com.domainname.testProduct1", "com.domainname.testProduct2", "com.domainname.testProduct3", } local function productListener( event ) for i = 1,#event.products do print( event.products[i].productIdentifier ) end end -- Load store products; store must be properly initialized by this point! store.loadProducts( productIdentifiers, productListener )
对于 `event.products` 中的每个实例,都会提供各种属性,这些属性可用于创建界面(商店场景)或以其他方式显示可用的产品。由于商店功能的核心差异,这些属性会因商店而异,但以下通用属性适用于所有商店的产品:
当用户/玩家选择在您的应用内购买产品时,将与商店的服务器建立连接以启动交易。然后,商店将处理交易并将结果数据发送回您的应用。如上所述,此事件由交易侦听器函数处理
您的交易侦听器函数应处理以下所有情况:
发生交易时,分派给交易侦听器函数的 `event.transaction` 表中将提供各种属性。由于商店功能的核心差异,这些属性会因商店而异,但以下通用属性适用于所有商店的交易:
state
— 指示交易状态的字符串,例如 `"purchased"`、`"cancelled"` 或 `"failed"`。identifier
— 交易的唯一字符串标识符。productIdentifier
— 表示与交易关联的产品标识符的字符串。receipt
— 交易收据的 JSON 格式字符串表示形式。date
— 表示交易发生日期的字符串。isError
— 布尔值,指示是否发生错误。如果为 `true`,则 `errorType` 和 `errorString` 将是说明原因的字符串。errorType
— 如果 `isError` 为 `true`,则表示发生的错误类型的字符串。errorString
— 如果 `isError` 为 `true`,则为更详细的错误消息(字符串)。收到交易事件后,您应该采取适当的行动。例如,如果用户成功购买了一件商品,您可以将此信息保存到文件或本地数据库中,并解锁使用该商品的功能。然后,可以在以后的会话中引用此文件,以便您知道该商品已被购买。
以下是交易侦听器函数的示例框架:
local function transactionListener( event ) local transaction = event.transaction if ( transaction.isError ) then print( transaction.errorType ) print( transaction.errorString ) else -- No errors; proceed if ( transaction.state == "purchased" or transaction.state == "restored" ) then -- Handle a normal purchase or restored purchase here print( transaction.state ) print( transaction.productIdentifier ) print( transaction.date ) elseif ( transaction.state == "cancelled" ) then -- Handle a cancelled transaction here elseif ( transaction.state == "failed" ) then -- Handle a failed transaction here end -- Tell the store that the transaction is complete -- If you're providing downloadable content, do not call this until the download has completed store.finishTransaction( transaction ) end end -- Initialize store store.init( transactionListener )
对于 Google IAP,此相同的交易侦听器函数还处理初始化(init)事件。因此,如果您使用的是 Google IAP,则应通过有条件地检查 event.name 的值来区分商店交易事件和初始化事件。有关执行此操作的示例,请参阅 Google IAP store.init() 文档。
如上所述,交易完成后,您必须在交易对象上调用 `store.finishTransaction()`。如果不这样做,商店会认为交易被中断,并会在下次应用程序启动时尝试恢复它。
end -- Tell the store that the transaction is complete -- If you're providing downloadable content, do not call this until the download has completed store.finishTransaction( transaction ) end end
要启动购买,请使用 `store.purchase()` 函数。调用此函数时,它将向商店提交购买请求,当商店完成交易处理后,将在 `store.init()` 中指定的侦听器函数将被调用。
store.purchase( productIdentifier )
如上面的交易属性中所述,交易侦听器函数中的 `event.transaction` 将收到 `state` 属性为 `"purchased"`,表示购买已成功处理。
如果用户清除设备上的信息或购买新设备,他/她需要一种方法来恢复以前从您的应用购买的商品(无需再次付费)。`store.restore()` 函数启动此过程
store.restore()
调用此函数时,将启动恢复过程,在此过程中,交易侦听器函数可能会被多次调用
如上所述,Apple 将为已恢复购买的 `state` 属性返回 `"restored"`,但 Google Play 和 Amazon 会将普通购买和已恢复购买统称为 `"purchased"` 状态。通常,您可以像上面交易属性中所示的示例一样,使用 `or` 运算符来处理这两种情况。
local transaction = event.transaction if ( transaction.state == "purchased" or transaction.state == "restored" ) then -- Handle a normal purchase or restored purchase here
每个市场都提供一些独特且可能至关重要的功能,您必须了解这些功能。以下列表总结了这些功能,但您应始终查阅iOS、Android和Amazon的文档,以检查
iOS 设备有一个可以禁用
Google IAP 要求您消耗购买的商品,才能再次购买该商品。本质上,一旦产品被购买,它就被视为“已拥有”,并且不能再次购买。但是,由于您几乎肯定希望鼓励玩家再次购买某些商品(例如宝石/硬币包、额外的生命等),因此您必须发送消耗请求将“已拥有”的产品恢复为“未拥有”产品,以便它们可以再次购买。消耗产品还会丢弃其以前的购买数据。
要消耗商品,请使用关联的产品标识符调用 store.consumePurchase()
store.consumePurchase( productIdentifier )
调用后,并在收到成功的消耗事件后,交易侦听器函数中 `event.transaction.state` 的值将为 `"consumed"`。此时,您可以
请注意,某些商品设计为只能购买一次,您不应消耗它们。例如,如果购买解锁了游戏中的新世界或授予角色永久性的
Google IAP 允许退款交易(说明)。收到成功的退款事件后,交易侦听器函数中 `event.transaction.state` 的值将为 `"refunded"`,在这种情况下,您可以禁用已退款的内容和/或将其从本地应用中删除(如果已下载)。
Amazon 允许您测试