通过允许用户购买或解锁内容或功能,了解如何在此应用内购买教程中增加应用收益。
Pietro Rea中级·文章·30分钟
更新说明:Pietro Rea为Xcode 10,Swift 4.2和iOS 11/12更新了本教程。Ray Wenderlich写了原文。
构建iOS应用程序的一个好处是,您在应用程序货币化方面有很多选择:普通的vanilla付费应用程序,广告支持的免费应用程序,甚至支持应用程序内购买的应用程序。
一个应用程序内购买(或IAP),允许开发人员在使用一个应用程序来收取特定功能或内容的用户。由于以下几个原因,实施IAP尤其引人注目:
- 这是一种额外的赚钱方式,除了简单地预先付费购买应用程序。一些用户愿意在额外的内容或功能上花费更多。
- 一个应用程序可以免费提供,这使其成为大多数人的简单下载。免费的应用程序通常会得到很多比付费应用下载量。如果用户喜欢该应用,那么他们可以在以后购买更多内容或功能。
- 您可以在免费应用程序中向用户显示广告,并可选择通过购买IAP删除它们。
- 在首次发布应用后,新的付费内容可以添加到同一个应用中,而不必开发全新的应用以赚取更多收益。
在此应用内购买教程中,您将利用IAP解锁应用中嵌入的额外内容。您需要熟悉基本的Swift和iOS编程概念。如果这些是不熟悉的主题,那么 在开始之前查看我们的Swift教程范围。您还需要一个付费开发者帐户,可以访问iOS开发人员中心 和App Store Connect。
入门
在这个应用程序内购买教程中,您将构建一个名为“RazeFaces”的小应用程序,它允许用户购买“RazeFace”,这是本网站常用的书籍和视频的简洁插图。
典型的“RazeFace”
使用顶部的链接下载材料,然后在Xcode中打开入门项目。构建并运行以查看它到目前为止的功能。答案是:不是很多!您将看到一个空表视图,导航栏中有一个“恢复”按钮,稍后将连接该按钮以恢复购买。
完成本教程后,将会在表格视图中列出一个您可以购买的RazeFaces列表。如果您删除并重新安装该应用程序,“ 还原”按钮将恢复以前购买的任何RazeFaces。
前往Xcode快速查看代码。主视图控制器位于MasterViewController.swift中。此类显示表视图,其中包含可用IAP列表。购买存储为SKProduct
对象数组。
请注意,MasterViewController
正在使用一个名为RazeFaceProducts.store
type 的对象IAPHelper
来执行繁重的操作。看看他们各自的代码文件RazeFaceProducts.swift和IAPHelper.swift。
RazeFaceProducts
是一个简单的结构,包含有关应用程序中产品的一些信息,并IAPHelper
完成与StoreKit交谈的所有重要工作。这些方法目前都已被删除,但您将在本教程中填写它们以向应用程序添加IAP功能。
在编写任何代码以合并IAP之前,您首先需要在iOS开发人员中心和App Store Connect中进行一些设置。
创建应用程序ID
首先,您需要创建一个App ID。这会将您的应用与您的应用内购买产品相关联。登录Apple开发人员中心,然后选择证书,ID和配置文件。
接下来,选择标识符>应用程序ID,然后单击右上角的+以创建新的应用程序ID。
填写新App ID的信息。输入RazeFace IAP教程应用程序的名称。选择显式应用程序ID并输入唯一的捆绑ID。通常的做法是反向使用您的域名(例如,com.razeware.razefaces)。记下Bundle ID,因为在接下来的步骤中将需要它。
向下滚动到“ 应用服务”部分。请注意, 默认情况下启用应用程序内购买 和GameCenter。单击继续,然后单击注册和完成。
恭喜!您有一个新的App ID!接下来,您将在App Store Connect中创建匹配的应用程序。
检查您的协议
在将iTunes添加到iTunes Connect中的应用程序之前,您必须执行以下两项操作:
- 确保您已在developer.apple.com上接受最新的Apple开发计划许可协议。
- 确保您已在App Store Connect的“协议,税金和结算”部分中接受了最新的付费应用程序协议。
如果您还没有这样做,通常iTunes Connect会给您一个警告,如下所示:
如果您看到类似上述内容,请按照步骤接受相应的协议。
在iTunes Connect中仔细检查协议,税和银行部分也是很好的:
如果您看到标题为“ 包含付费应用程序行的请求合同”的部分,请单击“ 请求”按钮。填写所有必要信息并提交。您的申请可能需要一段时间才能获得批准。稳坐!
否则,如果您看到有效合同中列出的付费应用程序,那么您似乎已经完成了此步骤!不错的工作!
注意:提交后,Apple可能需要数天才能批准这些与IAP相关的协议。在此期间,即使您在代码中正确实现了所有内容,也无法在应用中显示IAP产品。对于首次实施应用内购买的人来说,这是令人沮丧的常见原因。在那里挂!
在iTunes Connect中创建应用程序
现在要创建应用程序记录本身,单击 页面左上角的App Store Connect,然后单击 我的应用程序。
接下来,单击页面左上角的+,然后选择新建应用程序 以添加新的应用程序记录。填写如下所示的信息:
您将无法使用您在此处看到的完全相同的应用程序名称,因为应用程序名称在App Store中必须是唯一的。也许在上面屏幕截图中显示的示例标题之后添加您自己的首字母。
注意:如果您快速完成此步骤,则可能未在下拉列表中显示Bundle ID。这有时需要一段时间才能通过Apple的系统传播。
单击“ 创建”,您就完成了!
创建应用内购买产品
提供IAP时,您必须首先在App Store Connect中为每个单独的购买添加条目。如果您曾在商店中列出待售的应用程序,那么这是一个类似的过程,包括选择购买的定价等级。当用户进行购买时,App Store处理向用户收费的复杂过程并回复有关此类操作的数据。
您可以添加一大堆不同类型的IAP:
- 消耗品:这些可以多次购买,可以用完。这些非常适合额外的生活,游戏内货币,临时加电等。
- 非消耗品:您购买一次的东西,并期望具有永久性,如额外的水平和可解锁的内容。本教程中的RazeFace插图属于此类别。
- 非续订订阅:在固定时间段内可用的内容。
- 自动续订订阅:重复订阅,例如每月raywenderlich.com订阅。
您只能为数字商品提供应用内购买,而不能为实体商品或服务提供应用内购买。有关所有这些的更多信息,请查看Apple关于创建应用程序内购买产品的完整文档。
现在,在App Store Connect中查看应用程序的条目时,单击“ 功能” 选项卡,然后选择“ 应用程序内购买”。要添加新的IAP产品,请单击应用内购买右侧的+。
您将看到以下对话框:
当用户在您的应用中购买RazeFace时,您会希望他们始终可以访问它,因此请选择“ 非耗材”,然后单击“ 创建”。
接下来,填写IAP的详细信息如下:
- 参考名称:标识iTunes Connect中IAP的昵称。此名称不会出现在应用中的任何位置。您将通过此次购买解锁的RazeFace的标题是Swift Shopping,因此请在此处输入。
- 产品ID:这是标识IAP的唯一字符串。通常最好从Bundle ID开始,然后附加一个特定于此可购买项目的唯一名称。对于本教程,请确保添加swiftshopping,因为稍后将在应用程序中使用它来查找RazeFace以解锁。例如,您可以使用:com.theNameYouPickedEarlier.razefaces.swiftshopping。
- 清算待售:启用或禁用IAP的销售。你想启用它!
- 价格层:IAP的成本。选择第1层。
现在向下滚动到“ 本地化”部分,并注意英语(美国)有一个默认条目。为显示名称和描述输入“Swift Shopping” 。单击保存。大!您已经创建了第一个IAP产品。
注意:App Store Connect可能会抱怨您缺少IAP的元数据。在您提交应用以供审核之前,您需要在此页面底部添加IAP的屏幕截图。该屏幕截图仅用于Apple的评论,不会出现在App Store列表中。
在深入研究一些代码之前还需要一个步骤。在应用程序的开发版本中测试应用程序内购买时,Apple提供了一个测试环境,允许您“购买”您的IAP产品,而无需创建财务交易。
这些特殊测试购买只能通过App Store Connect中的特殊“Sandbox Tester”用户帐户进行。我保证,你几乎都在代码中!
创建沙盒用户
在App Store Connect中,单击窗口左上角的App Store Connect以返回主菜单。选择“ 用户和角色”,然后单击“ 沙箱测试器”选项卡。单击“Tester”标题旁边的+。
填写信息,完成后单击“ 保存”。您可以为测试用户组成名字和姓氏,但必须使用真实的电子邮件地址,因为Apple会向该地址发送验证电子邮件。收到该电子邮件后,请务必点击其中的链接以验证您的地址。
您输入的电子邮件地址也不应与Apple ID帐户相关联。提示:如果您有一个Gmail帐户,您只需使用地址别名,而不必创建一个全新的帐户。
注意:不幸的是,测试新购买的非耗材 IAP需要每次都有一个新的沙箱测试仪(和电子邮件地址)。使用相同沙盒测试器的重复购买将被视为恢复已购买的项目,因此不会执行特定于新购买的任何代码。
如果需要通过新的购买代码进行多次测试并且您的电子邮件提供商不支持限定符,那么请考虑设置可消耗的IAP仅用于测试目的。每次测试后删除设备上的应用程序,购买耗材IAP将被视为新购买。
您可以采用的一种策略是在测试成功案例之前尽可能多次测试失败案例。这样你就需要创建更少的沙盒测试器。一般情况下,请记住,一旦用户(甚至是沙盒)购买了非消耗性IAP,他就不能再次购买,只能恢复它。
太棒了 - 你现在有一个测试用户。您最终可以在您的应用中实施IAP!
项目配置
为了使一切正常工作,应用程序中的包标识符和产品标识符与您在开发人员中心和App Store Connect中创建的标识符和产品标识符相匹配非常重要。
转到Xcode的初学者项目。在Project导航器中选择RazeFaces项目,然后在Targets下再次选择它。选择常规选项卡,将您的团队切换到正确的团队,然后输入您之前使用的捆绑ID。
接下来选择Capabilities 选项卡。向下滚动到In-App Purchase并将开关切换到ON。
注意:如果IAP未显示在列表中,请确保在Xcode首选项的“帐户”部分中使用您用于创建应用程序ID的Apple ID登录。
打开RazeFaceProducts.swift。请注意,您创建的IAP产品有一个占位符引用:SwiftShopping
。将其替换为您在App Store Connect中配置的完整产品ID - 例如:
public static let SwiftShopping = "com.theNameYouPickedEarlier.razefaces.swiftshopping"
注意:可以从Web服务器中提取产品标识符列表,以便可以动态添加新的IAP,而不需要更新应用程序。本教程简单易用,使用硬编码的产品标识符。
列出应用内购买
该store
物业RazeFaceProducts
是一个实例IAPHelper
。如前所述,此对象与StoreKitAPI 交互以列出和执行购买。您的第一个任务是更新IAPHelper
以检索IAP列表 - 目前只有一个 - 来自Apple的服务器。
打开IAPHelper.swift。在该类的顶部,添加以下私有属性:
private let productIdentifiers: Set
接下来,init(productIds:)
在调用之前添加以下内容super.init()
:
productIdentifiers = productIds
一个IAPHelper
实例是通过将一组产品标识的创建。这是RazeFaceProducts
创建其store
实例的方式。
接下来,在刚才添加的那个下添加其他私有属性:
private var purchasedProductIdentifiers: Set = []
private var productsRequest: SKProductsRequest?
private var productsRequestCompletionHandler: ProductsRequestCompletionHandler?
purchasedProductIdentifiers
跟踪已购买的商品。SKProductsRequest
委托使用其他两个属性来执行对Apple服务器的请求。
接下来,仍然在IAPHelper.swift中替换以下的实现requestProducts(_:)
:
public func requestProducts(completionHandler: @escaping ProductsRequestCompletionHandler) {
productsRequest?.cancel()
productsRequestCompletionHandler = completionHandler
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productsRequest!.delegate = self
productsRequest!.start()
}
此代码保存用户的完成处理程序以供将来执行。然后,它通过SKProductsRequest
对象创建并向Apple发起请求。有一个问题:代码声明IAPHelper
为请求的委托,但它还不符合SKProductsRequestDelegate
协议。
要解决此问题,请在最后一个大括号之后将以下扩展名添加到IAPHelper.swift的最后:
// MARK: - SKProductsRequestDelegate
extension IAPHelper: SKProductsRequestDelegate {
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("Loaded list of products...")
let products = response.products
productsRequestCompletionHandler?(true, products)
clearRequestAndHandler()
for p in products {
print("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)")
}
}
public func request(_ request: SKRequest, didFailWithError error: Error) {
print("Failed to load list of products.")
print("Error: \(error.localizedDescription)")
productsRequestCompletionHandler?(false, nil)
clearRequestAndHandler()
}
private func clearRequestAndHandler() {
productsRequest = nil
productsRequestCompletionHandler = nil
}
}
此扩展用于通过实现SKProductsRequestDelegate
协议所需的两种方法从Apple服务器获取产品列表,标题,描述和价格。
productsRequest(_:didReceive:)
在成功检索列表时调用。它接收一个SKProduct
对象数组并将它们传递给先前保存的完成处理程序。处理程序使用新数据重新加载表。如果出现问题,request(_:didFailWithError:)
则调用。在任何一种情况下,当请求完成时,请求和完成处理程序都将被清除clearRequestAndHandler()
。
建立并运行。万岁!表格视图中显示了产品列表(目前只有一个)!这需要一些工作,但最终你到了那里。
注意:您可以在iOS模拟器和物理iOS设备上显示IAP产品,但如果您要测试购买或恢复购买,则只能在物理设备上执行此操作。更多相关信息,请参阅下面的采购部分。
注意:如果运行不成功且您没有看到任何产品,那么有很多事情需要检查。此列表由本帖的早期版本的论坛中的itsme.manish和abgtan提供,以及随着时间的推移添加的更多提示。
- 项目的Bundle ID是否与iOS开发中心的App ID相匹配?
- 制作时是否使用完整的产品ID
SKProductRequest
?(检查productIdentifiers
属性RazeFaceProducts
。) - 付费应用程序合同是否对iTunes Connect有效?他们提交申请的时间可能需要数小时到数天才能从待定到接受。
- 自从将产品添加到App Store Connect后,您有几个小时的时间吗?产品添加可能会立即生效或可能需要一些时间。
- 检查Apple Developer System状态。或者,尝试此链接。如果它没有响应状态值,则iTunes沙箱可能已关闭。Apple的Validating Receipts与App Store文档中说明了状态代码。
- 是否为App ID启用了IAP?(你之前选择过清仓吗?)
- 您是否尝试从设备中删除该应用并重新安装?
仍然卡住?正如您所看到的,IAP需要做很多工作。尝试本教程的评论与其他读者讨论。
购买物品
您希望能够确定已购买的商品。为此,您将使用purchasedProductIdentifiers
之前添加的 属性。如果此集中包含产品标识符,则用户已购买该项目。检查这个的方法很简单。
在IAPHelper.swift中,将return
语句 替换isProductPurchased(_:)
为以下内容:
return purchasedProductIdentifiers.contains(productIdentifier)
在本地保存购买状态可以减少每次应用启动时向Apple服务器请求此类数据的需求。purchasedProductIdentifiers
使用保存UserDefaults
。
仍在IAPHelper.swift中,替换init(productIds:)
为以下内容:
public init(productIds: Set) {
productIdentifiers = productIds
for productIdentifier in productIds {
let purchased = UserDefaults.standard.bool(forKey: productIdentifier)
if purchased {
purchasedProductIdentifiers.insert(productIdentifier)
print("Previously purchased: \(productIdentifier)")
} else {
print("Not purchased: \(productIdentifier)")
}
}
super.init()
}
对于每个产品标识符,检查是否存储该值UserDefaults
。如果是,则将标识符插入到purchasedProductIdentifiers
集合中。之后,您将在购买后向集合中添加标识符。
注意:用户默认值可能不是在实际应用程序中存储有关已购买产品的信息的最佳位置。越狱设备的所有者可以轻松访问您的应用程序的UserDefaults
plist,并将其修改为“解锁”购买。如果这种事情与您有关,那么值得查看Apple关于验证App Store收据的文档- 这可以让您验证用户是否进行了特定购买。
购物(给我看钱!)
了解用户购买的产品很棒,但您仍然需要首先进行购买!实施购买能力是合乎逻辑的下一步。
仍在IAPHelper.swift中,替换buyProduct(_:)
为以下内容:
public func buyProduct(_ product: SKProduct) {
print("Buying \(product.productIdentifier)...")
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
这将使用SKProduct
(从Apple服务器检索)创建支付对象以添加到支付队列。该代码使用一个SKPaymentQueue
名为的单例对象default()
。繁荣!钱在银行里。或者是吗?你怎么知道付款是否通过?
付款验证是通过IAPHelper
观察交易发生的SKPaymentQueue
。在设置IAPHelper
为SKPaymentQueue
事务观察器之前,该类必须符合SKPaymentTransactionObserver
协议。
转到IAPHelper.swift的最底部(在最后一个大括号之后)并添加以下扩展名:
// MARK: - SKPaymentTransactionObserver
extension IAPHelper: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
complete(transaction: transaction)
break
case .failed:
fail(transaction: transaction)
break
case .restored:
restore(transaction: transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
private func complete(transaction: SKPaymentTransaction) {
print("complete...")
deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restore(transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
print("restore... \(productIdentifier)")
deliverPurchaseNotificationFor(identifier: productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func fail(transaction: SKPaymentTransaction) {
print("fail...")
if let transactionError = transaction.error as NSError?,
let localizedDescription = transaction.error?.localizedDescription,
transactionError.code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(localizedDescription)")
}
SKPaymentQueue.default().finishTransaction(transaction)
}
private func deliverPurchaseNotificationFor(identifier: String?) {
guard let identifier = identifier else { return }
purchasedProductIdentifiers.insert(identifier)
UserDefaults.standard.set(true, forKey: identifier)
NotificationCenter.default.post(name: .IAPHelperPurchaseNotification, object: identifier)
}
}
这是很多代码!详细的审查是有序的。幸运的是,每种方法都很短。
paymentQueue(_:updatedTransactions:)
是协议实际需要的唯一方法。当一个或多个事务状态发生变化时,它会被调用。此方法评估更新事务数组中每个事务的状态,并调用相关的帮助方法:complete(transaction:)
,restore(transaction:)
或fail(transaction:)
。
如果交易已完成或已恢复,则会将其添加到购买集并将标识符保存在其中UserDefaults
。它还会在该事务中发布通知,以便应用程序中的任何感兴趣的对象都可以监听它以执行更新用户界面等操作。最后,在成功或失败的情况下,它将交易标记为已完成。
剩下的就是IAPHelper
作为支付交易观察员。仍然在IAPHelper.swift,回去init(productIds:)
和右侧添加以下行之后 super.init()
。
SKPaymentQueue.default().add(self)
购买沙箱
构建并运行应用程序 - 但要测试购买,您必须在设备上运行它。之前创建的沙箱测试仪可用于执行购买而无需收费。如果只有我可以让沙箱测试人员去购买我的杂货店:]以下是如何使用测试人员帐户:
转到您的iPhone并确保您已退出正常的App Store帐户。要执行此操作,请转到“ 设置”应用,然后点按“ iTunes和App Store”。
点按您的iCloud帐户名称,然后点按退出。此时,实际上并未使用沙箱用户登录。一旦您尝试在示例应用程序中购买IAP,系统将提示您执行此操作。
连接您的设备,构建并运行!您会在应用中看到您的产品。要开始购买,请点按“ 购买”按钮。
将出现一个提示您登录的警报。点击使用现有Apple ID,然后输入您之前创建的沙箱测试人员帐户的登录详细信息。
点按“购买”确认购买。警报视图显示正在沙盒中进行购买,以提醒您不会向您收取费用。
最后,将出现一个警报视图,确认购买成功。购买过程完成后,购买项目旁边会出现一个复选标记。点击购买的商品即可享受新的RazeFace。
最后你会看到这个“Swift Shopping”RazeFace,你一直听到这么多!
恢复购买
如果用户删除并重新安装应用程序或将其安装在其他设备上,则他们需要能够访问以前购买的项目。事实上,如果苹果无法恢复非消费品购买,Apple可能会拒绝该应用。
作为购买交易观察员,IAPHelper
在购买恢复时已经收到通知。下一步是通过恢复购买来对此通知做出反应。
打开IAPHelper.swift并滚动到文件的底部。在StoreKit API扩展中,替换restorePurchases()
为以下内容:
public func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
}
那太简单了!您已经设置了事务观察器并实现了方法来处理上一步中的恢复事务。
要对此进行测试,请在上一步中完成购买后,从设备中删除该应用。再次构建并运行,然后点击右上角的“恢复”。您应该会在先前购买的产品旁边看到复选标记。
付款权限
某些设备和帐户可能不允许进行应用内购买。例如,如果将父级控件设置为禁止它,则会发生这种情况。Apple要求优雅地处理这种情况。不这样做可能会导致应用拒绝。
再次打开IAPHelper.swift。在StoreKit API扩展中,使用以下行替换return
语句canMakePayments()
:
return SKPaymentQueue.canMakePayments()
产品单元格的行为应根据返回的值而有所不同canMakePayments()
。例如,如果canMakePayments()
退货false
,则不应显示“ 购买”按钮,并且应将价格替换为“不可用”。
要完成此任务,请打开ProductCell.swift并使用以下内容替换product
属性didSet
处理程序的整个实现:
didSet {
guard let product = product else { return }
textLabel?.text = product.localizedTitle
if RazeFaceProducts.store.isProductPurchased(product.productIdentifier) {
accessoryType = .checkmark
accessoryView = nil
detailTextLabel?.text = ""
} else if IAPHelper.canMakePayments() {
ProductCell.priceFormatter.locale = product.priceLocale
detailTextLabel?.text = ProductCell.priceFormatter.string(from: product.price)
accessoryType = .none
accessoryView = self.newBuyButton()
} else {
detailTextLabel?.text = "Not available"
}
}
当无法使用设备付款时,此实施将显示更合适的信息。而且你有它 - 一个应用程序内购买的应用程序!
然后去哪儿?
您可以使用本教程顶部或底部的“ 下载材料”按钮下载项目的完整版本。您可以在自己的项目中重复使用IAP帮助程序类!
在 应用程序内购买视频教程系列 由山姆·戴维斯涵盖了所有的介绍到这里的主题,但进入到一个新的水平在他谈到验证收据3部分。
示例应用程序的一个缺点是,它不会向用户表明何时与Apple通信。可能的改进是在适当的时间显示旋转器或HUD控制。但是,此UI增强超出了本教程的范围。有关HUD控件的更多信息,请查看iOS Apprentice的第3部分。
Apple有一个很棒的登录页面可供应用内购买:面向开发人员的应用内购买。它汇集了所有相关文档和WWDC视频的链接。
IAP可以成为您业务模式的重要组成部分。明智地使用它们并确保遵循有关恢复购买和优雅失败的指导方针,您将走向成功的道路!
如果您对此应用内购买教程有任何疑问或意见,请加入以下论坛讨论!
原文: https://www.raywenderlich.com/5456-in-app-purchase-tutorial-getting-started