如何将应用内购买添加到 iOS 应用?详细信息是什么,是否有示例代码?
这意味着如何将应用内购买添加到 iOS 应用程序的所有类型
如何将应用内购买添加到 iOS 应用?详细信息是什么,是否有示例代码?
这意味着如何将应用内购买添加到 iOS 应用程序的所有类型
Swift 用户可以查看My Swift Answer 的这个问题。
或者,查看Yedidya Reiss 的答案,它将这个 Objective-C 代码翻译成 Swift。
这个答案的其余部分是用 Objective-C 编写的
My Apps
然后单击要添加购买的应用程序Features
标题,然后In-App Purchases
在左侧选择+
中间的图标non-consumable
。如果您要向用户发送实物物品,或者给他们一些他们可以多次购买的东西,您会选择consumable
.tld.websitename.appname.referencename
这将是最好的,例如,您可以使用com.jojodmo.blix.removeads
cleared for sale
然后选择价格等级为 1 (99¢)。第 2 层为 1.99 美元,第 3 层为 2.99 美元。如果您单击“我建议您使用第 1 层”,则完整列表可用view pricing matrix
,因为这通常是任何人为删除广告所支付的最高费用。add language
按钮,输入信息。这将全部显示给客户,所以不要放任何你不希望他们看到的东西hosting content with Apple
选择否screenshot for review
FOR NOW,我们跳过的所有内容都会返回。注册您的产品 ID 可能需要几个小时App Store Connect
,因此请耐心等待。
现在您已经在 App Store Connect 上设置了您的应用内购买信息,进入您的 Xcode 项目,然后转到应用程序管理器(方法和头文件所在位置顶部的蓝色页面状图标)单击您的应用程序在目标下(应该是第一个)然后转到一般。在底部,您应该看到linked frameworks and libraries
单击小加号并添加框架StoreKit.framework
如果您不这样做,则应用内购买将不起作用!
如果您使用 Objective-C 作为您的应用程序的语言,您应该跳过这五个步骤。否则,如果您使用的是 Swift,您可以在此处关注我的 Swift 答案,或者,如果您更喜欢使用 Objective-C 作为应用内购买代码但在您的应用程序中使用 Swift,您可以执行以下操作:
.h
通过转到File
> New
> File...
(Command ⌘</kbd> + N). This file will be referred to as "Your .h
file" in the rest of the tutorial
出现提示时,单击创建桥接头。这将是我们的桥接头文件。如果没有提示,请转到第 3 步。如果有提示,请跳过第 3 步,直接转到第 4 步。
在主项目文件夹中创建另一个.h
名为Bridge.h
的文件,然后转到应用程序管理器(蓝色页面状图标),然后在该Targets
部分中选择您的应用程序,然后单击Build Settings
。找到显示Swift Compiler - Code Generation的选项,然后将Objective-C Bridging Header选项设置为Bridge.h
在您的桥接头文件中,添加行#import "MyObjectiveCHeaderFile.h"
,其中MyObjectiveCHeaderFile
是您在第一步中创建的头文件的名称。因此,例如,如果您将头文件命名为InAppPurchase.h,您可以将该行添加#import "InAppPurchase.h"
到您的网桥头文件中。
.m
通过转到File
> New
> File...
(创建一个新的 Objective-C 方法 ( ) 文件Command ⌘</kbd> + N). Name it the same as the header file you created in step 1. For example, if you called the file in step 1 InAppPurchase.h, you would call this new file InAppPurchase.m. This file will be referred to as "Your .m
file" in the rest of the tutorial.
现在我们将进入实际的编码。将以下代码添加到您的.h
文件中:
BOOL areAdsRemoved;
- (IBAction)restore;
- (IBAction)tapsRemoveAds;
接下来,您需要将StoreKit
框架导入到您的.m
文件中,并在声明之后添加SKProductsRequestDelegate
和:SKPaymentTransactionObserver
@interface
#import <StoreKit/StoreKit.h>
//put the name of your view controller in place of MyViewController
@interface MyViewController() <SKProductsRequestDelegate, SKPaymentTransactionObserver>
@end
@implementation MyViewController //the name of your view controller (same as above)
//the code below will be added here
@end
现在将以下内容添加到您的.m
文件中,这部分变得复杂,所以我建议您阅读代码中的注释:
//If you have more than one in-app purchase, you can define both of
//of them here. So, for example, you could define both kRemoveAdsProductIdentifier
//and kBuyCurrencyProductIdentifier with their respective product ids
//
//for this example, we will only use one product
#define kRemoveAdsProductIdentifier @"put your product id (the one that we just made in App Store Connect) in here"
- (IBAction)tapsRemoveAds{
NSLog(@"User requests to remove ads");
if([SKPaymentQueue canMakePayments]){
NSLog(@"User can make payments");
//If you have more than one in-app purchase, and would like
//to have the user purchase a different product, simply define
//another function and replace kRemoveAdsProductIdentifier with
//the identifier for the other product
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kRemoveAdsProductIdentifier]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(@"User cannot make payments due to parental controls");
//this is called the user cannot make payments, most likely due to parental controls
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
SKProduct *validProduct = nil;
int count = [response.products count];
if(count > 0){
validProduct = [response.products objectAtIndex:0];
NSLog(@"Products Available!");
[self purchase:validProduct];
}
else if(!validProduct){
NSLog(@"No products available");
//this is called if your product id is not valid, this shouldn't be called unless that happens.
}
}
- (void)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (IBAction) restore{
//this is called when the user restores purchases, you should hook this up to a button
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(@"received restored transactions: %i", queue.transactions.count);
for(SKPaymentTransaction *transaction in queue.transactions){
if(transaction.transactionState == SKPaymentTransactionStateRestored){
//called when the user successfully restores a purchase
NSLog(@"Transaction state -> Restored");
//if you have more than one in-app purchase product,
//you restore the correct product for the identifier.
//For example, you could use
//if(productID == kRemoveAdsProductIdentifier)
//to get the product identifier for the
//restored purchases, you can use
//
//NSString *productID = transaction.payment.productIdentifier;
[self doRemoveAds];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
//if you have multiple in app purchases in your app,
//you can get the product identifier of this transaction
//by using transaction.payment.productIdentifier
//
//then, check the identifier against the product IDs
//that you have defined to check which product the user
//just purchased
switch(transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(@"Transaction state -> Purchasing");
//called when the user is in the process of purchasing, do not add any of your own code here.
break;
case SKPaymentTransactionStatePurchased:
//this is called when the user has successfully purchased the package (Cha-Ching!)
[self doRemoveAds]; //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(@"Transaction state -> Purchased");
break;
case SKPaymentTransactionStateRestored:
NSLog(@"Transaction state -> Restored");
//add the same code as you did from SKPaymentTransactionStatePurchased here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
//called when the transaction does not finish
if(transaction.error.code == SKErrorPaymentCancelled){
NSLog(@"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
现在你想添加你的代码,当用户完成交易时会发生什么,在本教程中,我们使用删除添加,你必须添加你自己的代码,当横幅视图加载时会发生什么。
- (void)doRemoveAds{
ADBannerView *banner;
[banner setAlpha:0];
areAdsRemoved = YES;
removeAdsButton.hidden = YES;
removeAdsButton.enabled = NO;
[[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:@"areAdsRemoved"];
//use NSUserDefaults so that you can load whether or not they bought it
//it would be better to use KeyChain access, or something more secure
//to store the user data, because NSUserDefaults can be changed.
//You're average downloader won't be able to change it very easily, but
//it's still best to use something more secure than NSUserDefaults.
//For the purpose of this tutorial, though, we're going to use NSUserDefaults
[[NSUserDefaults standardUserDefaults] synchronize];
}
如果您的应用程序中没有广告,您可以使用任何其他您想要的东西。例如,我们可以将背景的颜色设为蓝色。为此,我们希望使用:
- (void)doRemoveAds{
[self.view setBackgroundColor:[UIColor blueColor]];
areAdsRemoved = YES
//set the bool for whether or not they purchased it to YES, you could use your own boolean here, but you would have to declare it in your .h file
[[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:@"areAdsRemoved"];
//use NSUserDefaults so that you can load wether or not they bought it
[[NSUserDefaults standardUserDefaults] synchronize];
}
现在,在您的方法的某处viewDidLoad
,您将要添加以下代码:
areAdsRemoved = [[NSUserDefaults standardUserDefaults] boolForKey:@"areAdsRemoved"];
[[NSUserDefaults standardUserDefaults] synchronize];
//this will load wether or not they bought the in-app purchase
if(areAdsRemoved){
[self.view setBackgroundColor:[UIColor blueColor]];
//if they did buy it, set the background to blue, if your using the code above to set the background to blue, if your removing ads, your going to have to make your own code here
}
现在您已经添加了所有代码,进入您的.xib
orstoryboard
文件,并添加两个按钮,一个表示购买,另一个表示恢复。将tapsRemoveAds
IBAction
连接到您刚刚制作的购买按钮,然后连接restore
IBAction
到恢复按钮。该restore
操作将检查用户之前是否购买过应用内购买,如果他们还没有应用内购买,则免费为他们提供应用内购买。
接下来,进入App Store Connect,然后单击Users and Access
然后单击Sandbox Testers
标题,然后单击+
左侧显示的符号Testers
。您可以随意输入名字和姓氏,电子邮件不必是真实的 - 您只需要能够记住它即可。输入密码(您必须记住)并填写其余信息。我建议您将Date of Birth
日期设为用户年满 18 岁或以上。App Store Territory
必须在正确的国家。接下来,注销您现有的 iTunes 帐户(您可以在完成本教程后重新登录)。
现在,在你的 iOS 设备上运行你的应用程序,如果你尝试在模拟器上运行它,购买总是会出错,你必须在你的 iOS 设备上运行它。应用程序运行后,点击购买按钮。当系统提示您登录 iTunes 帐户时,请以我们刚刚创建的测试用户身份登录。接下来,当它要求您确认购买 99 美分或任何您设置的价格等级时,拍摄屏幕快照,这就是您要在 App Store Connect 上使用的内容。现在取消付款。screenshot for review
现在,前往App Store Connect,然后前往My Apps
> the app you have the In-app purchase on
> In-App Purchases
。然后单击您的应用内购买,然后单击应用内购买详细信息下的编辑。完成后,将您刚刚在 iPhone 上拍摄的照片导入计算机,并将其作为屏幕截图上传以供审核,然后在审核备注中输入您的TEST USER电子邮件和密码。这将有助于苹果在审查过程中。
完成此操作后,返回 iOS 设备上的应用程序,仍以测试用户帐户登录,然后单击购买按钮。这次,确认付款别担心,这不会向您的帐户收取任何费用,测试用户帐户免费获得所有应用内购买确认付款后,请确保用户实际购买您的产品时会发生什么发生。如果没有,那么这将是您的doRemoveAds
方法的错误。同样,我建议使用将背景更改为蓝色来测试应用内购买,但这不应该是您实际的应用内购买。如果一切正常,你就可以开始了!只需确保在将新二进制文件上传到 App Store Connect 时将应用内购买包含在其中!
记录: No Products Available
这可能意味着四件事:
kRemoveAdsProductIdentifier
上述代码中的标识符如果它第一次不起作用,请不要沮丧!不要放弃!我花了大约 5 个小时才可以让它工作,大约 10 个小时寻找正确的代码!如果您完全使用上面的代码,它应该可以正常工作。如果您有任何问题,请随时发表评论。
我希望这对所有希望在他们的 iOS 应用程序中添加应用内购买的人有所帮助。干杯!
只需将 Jojodmo 代码翻译成 Swift:
class InAppPurchaseManager: NSObject , SKProductsRequestDelegate, SKPaymentTransactionObserver{
//If you have more than one in-app purchase, you can define both of
//of them here. So, for example, you could define both kRemoveAdsProductIdentifier
//and kBuyCurrencyProductIdentifier with their respective product ids
//
//for this example, we will only use one product
let kRemoveAdsProductIdentifier = "put your product id (the one that we just made in iTunesConnect) in here"
@IBAction func tapsRemoveAds() {
NSLog("User requests to remove ads")
if SKPaymentQueue.canMakePayments() {
NSLog("User can make payments")
//If you have more than one in-app purchase, and would like
//to have the user purchase a different product, simply define
//another function and replace kRemoveAdsProductIdentifier with
//the identifier for the other product
let set : Set<String> = [kRemoveAdsProductIdentifier]
let productsRequest = SKProductsRequest(productIdentifiers: set)
productsRequest.delegate = self
productsRequest.start()
}
else {
NSLog("User cannot make payments due to parental controls")
//this is called the user cannot make payments, most likely due to parental controls
}
}
func purchase(product : SKProduct) {
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
func restore() {
//this is called when the user restores purchases, you should hook this up to a button
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
func doRemoveAds() {
//TODO: implement
}
/////////////////////////////////////////////////
//////////////// store delegate /////////////////
/////////////////////////////////////////////////
// MARK: - store delegate -
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
if let validProduct = response.products.first {
NSLog("Products Available!")
self.purchase(validProduct)
}
else {
NSLog("No products available")
//this is called if your product id is not valid, this shouldn't be called unless that happens.
}
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
NSLog("received restored transactions: \(queue.transactions.count)")
for transaction in queue.transactions {
if transaction.transactionState == .Restored {
//called when the user successfully restores a purchase
NSLog("Transaction state -> Restored")
//if you have more than one in-app purchase product,
//you restore the correct product for the identifier.
//For example, you could use
//if(productID == kRemoveAdsProductIdentifier)
//to get the product identifier for the
//restored purchases, you can use
//
//NSString *productID = transaction.payment.productIdentifier;
self.doRemoveAds()
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
break;
}
}
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .Purchasing: NSLog("Transaction state -> Purchasing")
//called when the user is in the process of purchasing, do not add any of your own code here.
case .Purchased:
//this is called when the user has successfully purchased the package (Cha-Ching!)
self.doRemoveAds() //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
NSLog("Transaction state -> Purchased")
case .Restored:
NSLog("Transaction state -> Restored")
//add the same code as you did from SKPaymentTransactionStatePurchased here
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
case .Failed:
//called when the transaction does not finish
if transaction.error?.code == SKErrorPaymentCancelled {
NSLog("Transaction state -> Cancelled")
//the user cancelled the payment ;(
}
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
case .Deferred:
// The transaction is in the queue, but its final status is pending external action.
NSLog("Transaction state -> Deferred")
}
}
}
}
这是为了补充我对 Swift 用户的 Objective-C 答案,以防止 Objective-C 答案变得太大。
首先,在appstoreconnect.apple.com上设置应用内购买。按照我的 Objective-C 答案的开头部分(步骤 1-13,在App Store Connect标题下)获取有关执行此操作的说明。
您的产品 ID 可能需要几个小时才能在 App Store Connect 中注册,因此请耐心等待。
现在您已经在 App Store Connect 上设置了应用内购买信息,我们需要将 Apple 的应用内购买框架添加StoreKit
到应用程序中。
进入您的 Xcode 项目,然后转到应用程序管理器(在您的应用程序文件所在的左侧栏顶部的蓝色页面状图标)。单击左侧目标下的应用程序(它应该是第一个选项),然后转到顶部的“功能”。在列表中,您应该会看到“应用内购买”选项。打开此功能,Xcode 将添加StoreKit
到您的项目中。
现在,我们要开始编码了!
首先,创建一个新的 swift 文件来管理您所有的应用内购买。我要打电话给它IAPManager.swift
。
在这个文件中,我们将创建一个新类,称为IAPManager
a SKProductsRequestDelegate
and SKPaymentTransactionObserver
。在顶部,确保您导入Foundation
并StoreKit
import Foundation
import StoreKit
public class IAPManager: NSObject, SKProductsRequestDelegate,
SKPaymentTransactionObserver {
}
接下来,我们将添加一个变量来定义应用内购买的标识符(您也可以使用enum
,如果您有多个 IAP,这将更容易维护)。
// This should the ID of the in-app-purchase you made on AppStore Connect.
// if you have multiple IAPs, you'll need to store their identifiers in
// other variables, too (or, preferably in an enum).
let removeAdsID = "com.skiplit.removeAds"
接下来让我们为我们的类添加一个初始化器:
// This is the initializer for your IAPManager class
//
// A better, and more scaleable way of doing this
// is to also accept a callback in the initializer, and call
// that callback in places like the paymentQueue function, and
// in all functions in this class, in place of calls to functions
// in RemoveAdsManager (you'll see those calls in the code below).
let productID: String
init(productID: String){
self.productID = productID
}
现在,我们将为SKProductsRequestDelegate
和SKPaymentTransactionObserver
工作添加所需的功能:
我们稍后会添加这个RemoveAdsManager
类
// This is called when a SKProductsRequest receives a response
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse){
// Let's try to get the first product from the response
// to the request
if let product = response.products.first{
// We were able to get the product! Make a new payment
// using this product
let payment = SKPayment(product: product)
// add the new payment to the queue
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
}
else{
// Something went wrong! It is likely that either
// the user doesn't have internet connection, or
// your product ID is wrong!
//
// Tell the user in requestFailed() by sending an alert,
// or something of the sort
RemoveAdsManager.removeAdsFailure()
}
}
// This is called when the user restores their IAP sucessfully
private func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
// For every transaction in the transaction queue...
for transaction in queue.transactions{
// If that transaction was restored
if transaction.transactionState == .restored{
// get the producted ID from the transaction
let productID = transaction.payment.productIdentifier
// In this case, we have only one IAP, so we don't need to check
// what IAP it is. However, this is useful if you have multiple IAPs!
// You'll need to figure out which one was restored
if(productID.lowercased() == IAPManager.removeAdsID.lowercased()){
// Restore the user's purchases
RemoveAdsManager.restoreRemoveAdsSuccess()
}
// finish the payment
SKPaymentQueue.default().finishTransaction(transaction)
}
}
}
// This is called when the state of the IAP changes -- from purchasing to purchased, for example.
// This is where the magic happens :)
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
for transaction in transactions{
// get the producted ID from the transaction
let productID = transaction.payment.productIdentifier
// In this case, we have only one IAP, so we don't need to check
// what IAP it is.
// However, if you have multiple IAPs, you'll need to use productID
// to check what functions you should run here!
switch transaction.transactionState{
case .purchasing:
// if the user is currently purchasing the IAP,
// we don't need to do anything.
//
// You could use this to show the user
// an activity indicator, or something like that
break
case .purchased:
// the user successfully purchased the IAP!
RemoveAdsManager.removeAdsSuccess()
SKPaymentQueue.default().finishTransaction(transaction)
case .restored:
// the user restored their IAP!
IAPTestingHandler.restoreRemoveAdsSuccess()
SKPaymentQueue.default().finishTransaction(transaction)
case .failed:
// The transaction failed!
RemoveAdsManager.removeAdsFailure()
// finish the transaction
SKPaymentQueue.default().finishTransaction(transaction)
case .deferred:
// This happens when the IAP needs an external action
// in order to proceeded, like Ask to Buy
RemoveAdsManager.removeAdsDeferred()
break
}
}
}
现在让我们添加一些可用于开始购买或恢复购买的功能:
// Call this when you want to begin a purchase
// for the productID you gave to the initializer
public func beginPurchase(){
// If the user can make payments
if SKPaymentQueue.canMakePayments(){
// Create a new request
let request = SKProductsRequest(productIdentifiers: [productID])
// Set the request delegate to self, so we receive a response
request.delegate = self
// start the request
request.start()
}
else{
// Otherwise, tell the user that
// they are not authorized to make payments,
// due to parental controls, etc
}
}
// Call this when you want to restore all purchases
// regardless of the productID you gave to the initializer
public func beginRestorePurchases(){
// restore purchases, and give responses to self
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
接下来,让我们添加一个新的实用程序类来管理我们的 IAP。所有这些代码都可以在一个类中,但是拥有多个代码会使它更干净一些。我将创建一个名为 的新类RemoveAdsManager
,并在其中放置一些函数
public class RemoveAdsManager{
class func removeAds()
class func restoreRemoveAds()
class func areAdsRemoved() -> Bool
class func removeAdsSuccess()
class func restoreRemoveAdsSuccess()
class func removeAdsDeferred()
class func removeAdsFailure()
}
前三个函数 、removeAds
和restoreRemoveAds
是areAdsRemoved
您将调用以执行某些操作的函数。最后四个是由 调用的IAPManager
。
让我们在前两个函数中添加一些代码,removeAds
并且restoreRemoveAds
:
// Call this when the user wants
// to remove ads, like when they
// press a "remove ads" button
class func removeAds(){
// Before starting the purchase, you could tell the
// user that their purchase is happening, maybe with
// an activity indicator
let iap = IAPManager(productID: IAPManager.removeAdsID)
iap.beginPurchase()
}
// Call this when the user wants
// to restore their IAP purchases,
// like when they press a "restore
// purchases" button.
class func restoreRemoveAds(){
// Before starting the purchase, you could tell the
// user that the restore action is happening, maybe with
// an activity indicator
let iap = IAPManager(productID: IAPManager.removeAdsID)
iap.beginRestorePurchases()
}
最后,让我们在最后五个函数中添加一些代码。
// Call this to check whether or not
// ads are removed. You can use the
// result of this to hide or show
// ads
class func areAdsRemoved() -> Bool{
// This is the code that is run to check
// if the user has the IAP.
return UserDefaults.standard.bool(forKey: "RemoveAdsPurchased")
}
// This will be called by IAPManager
// when the user sucessfully purchases
// the IAP
class func removeAdsSuccess(){
// This is the code that is run to actually
// give the IAP to the user!
//
// I'm using UserDefaults in this example,
// but you may want to use Keychain,
// or some other method, as UserDefaults
// can be modified by users using their
// computer, if they know how to, more
// easily than Keychain
UserDefaults.standard.set(true, forKey: "RemoveAdsPurchased")
UserDefaults.standard.synchronize()
}
// This will be called by IAPManager
// when the user sucessfully restores
// their purchases
class func restoreRemoveAdsSuccess(){
// Give the user their IAP back! Likely all you'll need to
// do is call the same function you call when a user
// sucessfully completes their purchase. In this case, removeAdsSuccess()
removeAdsSuccess()
}
// This will be called by IAPManager
// when the IAP failed
class func removeAdsFailure(){
// Send the user a message explaining that the IAP
// failed for some reason, and to try again later
}
// This will be called by IAPManager
// when the IAP gets deferred.
class func removeAdsDeferred(){
// Send the user a message explaining that the IAP
// was deferred, and pending an external action, like
// Ask to Buy.
}
把它们放在一起,我们得到这样的东西:
import Foundation
import StoreKit
public class RemoveAdsManager{
// Call this when the user wants
// to remove ads, like when they
// press a "remove ads" button
class func removeAds(){
// Before starting the purchase, you could tell the
// user that their purchase is happening, maybe with
// an activity indicator
let iap = IAPManager(productID: IAPManager.removeAdsID)
iap.beginPurchase()
}
// Call this when the user wants
// to restore their IAP purchases,
// like when they press a "restore
// purchases" button.
class func restoreRemoveAds(){
// Before starting the purchase, you could tell the
// user that the restore action is happening, maybe with
// an activity indicator
let iap = IAPManager(productID: IAPManager.removeAdsID)
iap.beginRestorePurchases()
}
// Call this to check whether or not
// ads are removed. You can use the
// result of this to hide or show
// ads
class func areAdsRemoved() -> Bool{
// This is the code that is run to check
// if the user has the IAP.
return UserDefaults.standard.bool(forKey: "RemoveAdsPurchased")
}
// This will be called by IAPManager
// when the user sucessfully purchases
// the IAP
class func removeAdsSuccess(){
// This is the code that is run to actually
// give the IAP to the user!
//
// I'm using UserDefaults in this example,
// but you may want to use Keychain,
// or some other method, as UserDefaults
// can be modified by users using their
// computer, if they know how to, more
// easily than Keychain
UserDefaults.standard.set(true, forKey: "RemoveAdsPurchased")
UserDefaults.standard.synchronize()
}
// This will be called by IAPManager
// when the user sucessfully restores
// their purchases
class func restoreRemoveAdsSuccess(){
// Give the user their IAP back! Likely all you'll need to
// do is call the same function you call when a user
// sucessfully completes their purchase. In this case, removeAdsSuccess()
removeAdsSuccess()
}
// This will be called by IAPManager
// when the IAP failed
class func removeAdsFailure(){
// Send the user a message explaining that the IAP
// failed for some reason, and to try again later
}
// This will be called by IAPManager
// when the IAP gets deferred.
class func removeAdsDeferred(){
// Send the user a message explaining that the IAP
// was deferred, and pending an external action, like
// Ask to Buy.
}
}
public class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver{
// This should the ID of the in-app-purchase you made on AppStore Connect.
// if you have multiple IAPs, you'll need to store their identifiers in
// other variables, too (or, preferably in an enum).
static let removeAdsID = "com.skiplit.removeAds"
// This is the initializer for your IAPManager class
//
// An alternative, and more scaleable way of doing this
// is to also accept a callback in the initializer, and call
// that callback in places like the paymentQueue function, and
// in all functions in this class, in place of calls to functions
// in RemoveAdsManager.
let productID: String
init(productID: String){
self.productID = productID
}
// Call this when you want to begin a purchase
// for the productID you gave to the initializer
public func beginPurchase(){
// If the user can make payments
if SKPaymentQueue.canMakePayments(){
// Create a new request
let request = SKProductsRequest(productIdentifiers: [productID])
request.delegate = self
request.start()
}
else{
// Otherwise, tell the user that
// they are not authorized to make payments,
// due to parental controls, etc
}
}
// Call this when you want to restore all purchases
// regardless of the productID you gave to the initializer
public func beginRestorePurchases(){
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
// This is called when a SKProductsRequest receives a response
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse){
// Let's try to get the first product from the response
// to the request
if let product = response.products.first{
// We were able to get the product! Make a new payment
// using this product
let payment = SKPayment(product: product)
// add the new payment to the queue
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
}
else{
// Something went wrong! It is likely that either
// the user doesn't have internet connection, or
// your product ID is wrong!
//
// Tell the user in requestFailed() by sending an alert,
// or something of the sort
RemoveAdsManager.removeAdsFailure()
}
}
// This is called when the user restores their IAP sucessfully
private func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
// For every transaction in the transaction queue...
for transaction in queue.transactions{
// If that transaction was restored
if transaction.transactionState == .restored{
// get the producted ID from the transaction
let productID = transaction.payment.productIdentifier
// In this case, we have only one IAP, so we don't need to check
// what IAP it is. However, this is useful if you have multiple IAPs!
// You'll need to figure out which one was restored
if(productID.lowercased() == IAPManager.removeAdsID.lowercased()){
// Restore the user's purchases
RemoveAdsManager.restoreRemoveAdsSuccess()
}
// finish the payment
SKPaymentQueue.default().finishTransaction(transaction)
}
}
}
// This is called when the state of the IAP changes -- from purchasing to purchased, for example.
// This is where the magic happens :)
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
for transaction in transactions{
// get the producted ID from the transaction
let productID = transaction.payment.productIdentifier
// In this case, we have only one IAP, so we don't need to check
// what IAP it is.
// However, if you have multiple IAPs, you'll need to use productID
// to check what functions you should run here!
switch transaction.transactionState{
case .purchasing:
// if the user is currently purchasing the IAP,
// we don't need to do anything.
//
// You could use this to show the user
// an activity indicator, or something like that
break
case .purchased:
// the user sucessfully purchased the IAP!
RemoveAdsManager.removeAdsSuccess()
SKPaymentQueue.default().finishTransaction(transaction)
case .restored:
// the user restored their IAP!
RemoveAdsManager.restoreRemoveAdsSuccess()
SKPaymentQueue.default().finishTransaction(transaction)
case .failed:
// The transaction failed!
RemoveAdsManager.removeAdsFailure()
// finish the transaction
SKPaymentQueue.default().finishTransaction(transaction)
case .deferred:
// This happens when the IAP needs an external action
// in order to proceeded, like Ask to Buy
RemoveAdsManager.removeAdsDeferred()
break
}
}
}
}
最后,您需要为用户添加一些方式来开始购买和通话RemoveAdsManager.removeAds()
,并开始恢复和通话RemoveAdsManager.restoreRemoveAds()
,就像某处的按钮!请记住,根据 App Store 指南,您确实需要提供一个按钮来在某处恢复购买。
最后要做的就是在 App Store Connect 上提交您的 IAP 以供审核!有关执行此操作的详细说明,您可以按照我的 Objective-C 答案的最后一部分,在提交以供审核标题下。
RMStore是一个用于应用内购买的轻量级 iOS 库。它包装了 StoreKit API,并为您提供了方便的异步请求块。购买产品就像调用一个方法一样简单。
对于高级用户,该库还提供收据验证、内容下载和事务持久性。
我知道我发布这个已经很晚了,但是当我了解 IAP 模型的原理时,我分享了类似的经验。
应用内购买是 Storekit 框架实现的 iOS 中最全面的工作流程之一。如果您耐心阅读,整个文档非常清晰,但在技术性方面有些先进。
总结一下:
1 - 请求产品 - 使用 SKProductRequest 和 SKProductRequestDelegate 类发出对产品 ID 的请求并从您自己的 itunesconnect 商店接收它们。
这些 SKProducts 应该用于填充用户可以用来购买特定产品的商店 UI。
2 - 发出付款请求 - 使用 SKPayment 和 SKPaymentQueue 将付款添加到交易队列。
3 - 监控交易队列的状态更新 - 使用 SKPaymentTransactionObserver 协议的 updatedTransactions 方法来监控状态:
SKPaymentTransactionStatePurchasing - don't do anything
SKPaymentTransactionStatePurchased - unlock product, finish the transaction
SKPaymentTransactionStateFailed - show error, finish the transaction
SKPaymentTransactionStateRestored - unlock product, finish the transaction
4 - 恢复按钮流程 - 使用 SKPaymentQueue 的 restoreCompletedTransactions 来完成此操作 - 步骤 3 将处理其余部分,以及 SKPaymentTransactionObserver 的以下方法:
paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError
这是一个解释它的分步教程(由我自己尝试理解它而编写)。最后,它还提供了您可以直接使用的代码示例。
这是我创建的另一篇文章,用于解释某些只有文字才能更好地描述的事物。