如何将应用内购买添加到iOS应用?


Answers:


554

迅捷的用户

Swift用户可以查看有关此问题的My Swift Answer
或者,查看Yedidya Reiss的Answer,它将这个Objective-C代码转换为Swift。

Objective-C用户

该答案的其余部分用Objective-C编写

App Store连接

  1. 转到appstoreconnect.apple.com并登录
  2. 点击My Apps然后点击您想要添加购买的应用程序
  3. 单击Features标题,然后In-App Purchases在左侧选择
  4. 点击+中间的图标
  5. 在本教程中,我们将添加应用内购买来删除广告,因此请选择non-consumable。如果您要向用户发送实物商品,或给他们一些他们可以购买多次的东西,则可以选择consumable
  6. 对于参考名称,请输入您想要的名称(但请确保您知道它是什么)
  7. 对于产品ID,tld.websitename.appname.referencename此方法效果最好,例如,您可以使用com.jojodmo.blix.removeads
  8. 选择cleared for sale,然后选择价格层为1(99¢)。第2层的价格为1.99美元,第3层的价格为2.99美元。如果您单击“ view pricing matrix我建议您使用第1层”,则完整列表可用。因为这通常是任何人为删除广告所付的最高价。
  9. 单击蓝色add language按钮,然后输入信息。这将全部显示给客户,所以不要放任何您不希望他们看到的东西
  10. 对于hosting content with Apple选择
  11. 您可以将复习笔记空白FOR NOW
  12. 跳过screenshot for review FOR NOW,一切我们跳过我们会回来。
  13. 点击“保存”

您的产品ID可能要花几个小时才能注册App Store Connect,因此请耐心等待。

设置项目

现在,您已经在App Store Connect上设置了应用程序内购买信息,进入Xcode项目,然后转到应用程序管理器(方法和头文件位于顶部的蓝色页面状图标),单击将您的应用置于目标下(应该是第一个),然后进入常规状态。在底部,你应该可以看到linked frameworks and libraries单击小加号,并添加框架StoreKit.framework。如果你不这样做,应用程序内购买将工作!

如果您将Objective-C用作应用程序的语言,则应跳过这五个步骤。否则,如果您使用的是Swift,则可以在此处回答“我的Swift答案”,或者,如果您更喜欢在应用内购买代码中使用Objective-C,但在应用中使用了Swift,则可以执行以下操作:

  1. 创建一个新.h的打算(头)文件File> New> File...Command ⌘+ N)。.h在本教程的其余部分中,此文件将称为“您的文件”

  2. 出现提示时,点击创建桥接标题。这将是我们的桥接头文件。如果你提示,请转到步骤3.如果您提示,请跳过步骤3,直接进入第4步。

  3. 在主项目文件夹中创建另一个.h文件Bridge.h,然后转到“应用程序管理器”(蓝色页面状图标),然后在该Targets部分中选择您的应用程序,然后单击“确定” Build Settings。找到显示Swift Compiler-Code Generation的选项,然后将Objective-C Bridging Header选项设置为Bridge.h

  4. 在桥接头文件中,添加line #import "MyObjectiveCHeaderFile.h",其中MyObjectiveCHeaderFile是您在第一步中创建的头文件的名称。因此,例如,如果您将头文件命名为InAppPurchase.h,则可以将该行添加#import "InAppPurchase.h"到网桥头文件中。

  5. 创建一个新的Objective-C的方法(.m通过转到)文件File> New> File...Command ⌘+ N)。将其命名为与在步骤1中创建的头文件相同的名称。例如,如果您在步骤1 InAppPurchase.h中调用了该文件,则将其命名为这个新文件InAppPurchase.m.m在本教程的其余部分中,此文件将称为“您的文件”。

编码

现在,我们将开始实际的编码。将以下代码添加到您的.h文件中:

BOOL areAdsRemoved;

- (IBAction)restore;
- (IBAction)tapsRemoveAds;

接下来,您需要将StoreKit框架导入.m文件中,SKProductsRequestDelegateSKPaymentTransactionObserver@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
}

现在,您已经添加了所有代码,进入您的.xibstoryboard文件,并添加了两个按钮,一个表示购买,另一个表示恢复。将挂钩tapsRemoveAds IBAction到您刚创建的购买按钮,并将挂钩restore IBAction到恢复按钮。该restore操作将检查用户是否以前购买了应用程序内购买,如果尚未购买,则免费为他们提供应用程序内购买。

提交审查

接下来,进入App Store Connect,单击,Users and Access然后单击Sandbox Testers标题,然后单击+左侧显示的符号Testers。您可以随意输入名字和姓氏,并且电子邮件不一定是真实的-您只需要能够记住它即可。输入密码(您必须记住该密码)并填写其余信息。我建议您指定Date of Birth一个使用户年满18岁的日期。App Store Territory HAS是在正确的国家。接下来,注销您现有的iTunes帐户(您可以在本教程后重新登录)。

现在,你的iOS设备上运行应用程序,如果你试图在模拟器上运行它,这次收购将永远错误,你你的iOS设备上运行。应用运行后,点击购买按钮。当提示您登录iTunes帐户时,以我们刚刚创建的测试用户身份登录。接下来,当它要求您确认购买99美分或您设置的价格等级时,请抓住它的屏幕快照,这是您打算screenshot for review在App Store Connect上使用的内容。现在取消付款。

现在,进入App Store的连接,然后去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

这可能意味着四件事:

  • 您未在代码中输入正确的应用内购买ID(kRemoveAdsProductIdentifier上述代码中的标识符)
  • 您没有在App Store Connect上清除要出售的应用内购买
  • 您没有等待在App Store Connect中注册应用程序内购买ID 。等待创建ID几个小时,您的问题应得到解决。
  • 您尚未完成填写协议,税收和银行信息。

如果第一次无法使用,请不要沮丧!不要放弃!我花了大约5个小时才能够完成此工作,并且花了大约10个小时来寻找正确的代码!如果您完全使用上面的代码,它应该可以正常工作。如有任何疑问请随时发表评论。

我希望这对希望将应用程序内购买添加到其iOS应用程序的所有人有所帮助。干杯!


1
);但如果我不加这一行,当我点击还原按钮没有发生..反正非常感谢你在本教程
伊拉里奥

1
“ if(* transaction * == SKPaymentTransactionStateRestored){–应该是if(* transaction.transactionState * == SKPaymentTransactionStateRestored){
Massmaker 2015年

13
Apple的最佳实践建议您将事务观察器添加到AppDelegate,而不是视图控制器操作。developer.apple.com/library/ios/technotes/tn2387/_index.html
Craig Pickering 2015年

3
我收到0件商品,但我已经检查了您列出的3个可能原因。如果我没有在iTunes上的“ ios付费应用程序合同”上设置联系信息,银行信息和税务信息,这是我唯一想到的事情,这可能是原因吗?
克里斯托弗·弗朗西斯科

4
您应该说明,在步骤9中,显示名称是显示给用户的。它的显示方式是:“您想以0.99美元的价格购买一个DISPLAY NAME吗?”。这很重要,因为我将显示名称设置为“删除广告”,然后我的应用被拒绝了,因为我在弹出窗口中使用了不正确的语法!我必须将显示名称更改为“广告删除包”。
艾伦·斯卡帕

13

只需将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")

        }


    }
}
} 

6

快速答案

这是为了补充对Swift用户的Objective-C答案,以防止Objective-C答案变得太大。

建立

首先,在appstoreconnect.apple.com上设置应用内购买。请按照我的Objective-C答案的开头部分(App Store Connect标头下的步骤1-13 )进行操作。

您的产品ID在App Store Connect中注册可能需要几个小时,因此请耐心等待。

现在,您已经在App Store Connect上设置了应用内购买信息,我们需要将Apple的应用内购买框架添加StoreKit到应用中。

进入Xcode项目,然后转到应用程序管理器(应用程序文件所在的左栏顶部的蓝色页面状图标)。在左侧目标下单击您的应用程序(它应该是第一个选项),然后转到顶部的“功能”。在列表上,您应该看到一个“应用内购买”选项。打开此功能,Xcode将添加StoreKit到您的项目中。

编码

现在,我们将开始编码!

首先,制作一个新的swift文件,该文件将管理您的所有应用内购买。我要称呼它IAPManager.swift

在此文件中,我们将创建一个新类,称为IAPManagera SKProductsRequestDelegateSKPaymentTransactionObserver。在顶部,请确保您导入FoundationStoreKit

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
}

现在,我们将添加所需的功能SKProductsRequestDelegateSKPaymentTransactionObserver使其工作:

我们稍后再添加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()
}

前三项功能,removeAdsrestoreRemoveAds,和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指南,您确实需要提供一个按钮以在某处恢复购买。

提交审查

最后要做的是将您的IAP提交给App Store Connect进行审核!有关执行此操作的详细说明,您可以按照我的Objective-C答案的最后一部分,在“ 提交审阅”标题下。



-1

我知道我发布这个帖子已经很晚了,但是当我学习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

是一个分步教程(由我自己尝试理解,由我撰写)对它进行了说明。最后,它还提供了可直接使用的代码示例。

是我创建的另一本书,用于解释某些内容,只有文本才能更好地描述。


21
StackOverflow是一个用于帮助他人的网站,而不是试图从中牟利。您应该删除倒数第二个链接,或者只是免费在此处发布该教程中的操作。
Jojodmo

@Jojodmo您可以通过SO的任何准则来证实您的主张吗?我看到很多人在推销自己的SDK(甚至是付费的)的免责声明,我认为这也很常见。
尼拉夫·巴特

12
没有针对它的指导方针,但是如果您在这里赚钱,您可能是出于错误的原因在这里。IMO,您的答案似乎侧重于让人们注册您的视频教程,而不是帮助他人
Jojodmo

3
这不过是烦恼而已。
durazno 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.