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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19556336/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 02:32:11  来源:igfitidea点击:

How do you add an in-app purchase to an iOS application?

iosobjective-cswiftcocoa-touchin-app-purchase

提问by Jojodmo

How do you add an in-app purchase to an iOS app? What are all the details and is there any sample code?

如何将应用内购买添加到 iOS 应用?所有细节是什么,是否有任何示例代码?

This is meant to be a catch-all of sorts for how to add in-app purchases to iOS apps

这是关于如何向 iOS 应用程序添加应用程序内购买的各种信息

回答by Jojodmo

Swift Users

迅捷用户

Swift users can check out My Swift Answer for this question.
Or, check out Yedidya Reiss's Answer, which translates this Objective-C code to Swift.

Swift 用户可以查看我的 Swift Answer 来了解这个问题
或者,查看Yedidya Reiss's Answer,它将此 Objective-C 代码转换为 Swift。

Objective-C Users

Objective-C 用户

The rest of this answer is written in Objective-C

这个答案的其余部分是用 Objective-C 编写的

App Store Connect

应用商店连接

  1. Go to appstoreconnect.apple.comand log in
  2. Click My Appsthen click the app you want do add the purchase to
  3. Click the Featuresheader, and then select In-App Purchaseson the left
  4. Click the +icon in the middle
  5. For this tutorial, we are going to be adding an in-app purchase to remove ads, so choose non-consumable. If you were going to send a physical item to the user, or give them something that they can buy more than once, you would choose consumable.
  6. For the reference name, put whatever you want (but make sure you know what it is)
  7. For product id put tld.websitename.appname.referencenamethis will work the best, so for example, you could use com.jojodmo.blix.removeads
  8. Choose cleared for saleand then choose price tier as 1 (99¢). Tier 2 would be $1.99, and tier 3 would be $2.99. The full list is available if you click view pricing matrixI recommend you use tier 1, because that's usually the most anyone will ever pay to remove ads.
  9. Click the blue add languagebutton, and input the information. This will ALL be shown to the customer, so don't put anything you don't want them seeing
  10. For hosting content with Applechoose no
  11. You can leave the review notes blank FOR NOW.
  12. Skip the screenshot for reviewFOR NOW, everything we skip we will come back to.
  13. Click 'save'
  1. 前往appstoreconnect.apple.com并登录
  2. 单击My Apps然后单击您想要添加购买的应用程序
  3. 单击Features标题,然后In-App Purchases在左侧选择
  4. 点击+中间的图标
  5. 在本教程中,我们将添加一个应用内购买来移除广告,所以选择non-consumable。如果您要向用户发送实物商品,或者给他们一些他们可以多次购买的商品,您会选择consumable.
  6. 对于参考名称,输入您想要的任何内容(但请确保您知道它是什么)
  7. 对于产品 id puttld.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 reviewFOR NOW,我们跳过的所有内容都会回来。
  13. 点击“保存”

It could take a few hours for your product ID to register in App Store Connect, so be patient.

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

Setting up your project

设置您的项目

Now that you've set up your in-app purchase information on App Store Connect, go into your Xcode project, and go to the application manager (blue page-like icon at the top of where your methods and header files are) click on your app under targets (should be the first one) then go to general. At the bottom, you should see linked frameworks and librariesclick the little plus symbol and add the framework StoreKit.frameworkIf you don't do this, the in-app purchase will NOTwork!

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

If you are using Objective-C as the language for your app, you should skip these five steps. Otherwise, if you are using Swift, you can follow My Swift Answer for this question, here, or, if you prefer to use Objective-C for the In-App Purchase code but are using Swift in your app, you can do the following:

如果你使用 Objective-C 作为你的应用程序语言,你应该跳过这五个步骤。否则,如果您使用的是 Swift,则可以在此处按照我的 Swift 回答对此问题进行操作,或者,如果您更喜欢将 Objective-C 用于应用内购买代码但在您的应用中使用 Swift,则可以执行以下操作:

  1. Create a new .h(header) file by going to File> New> File...(Command ?+ N). This file will be referred to as "Your .hfile" in the rest of the tutorial

  2. When prompted, click Create Bridging Header. This will be our bridging header file. If you are notprompted, go to step 3. If you areprompted, skip step 3 and go directly to step 4.

  3. Create another .hfile named Bridge.hin the main project folder, Then go to the Application Manager (the blue page-like icon), then select your app in the Targetssection, and click Build Settings. Find the option that says Swift Compiler - Code Generation, and then set the Objective-C Bridging Headeroption to Bridge.h

  4. In your bridging header file, add the line #import "MyObjectiveCHeaderFile.h", where MyObjectiveCHeaderFileis the name of the header file that you created in step one. So, for example, if you named your header file InAppPurchase.h, you would add the line #import "InAppPurchase.h"to your bridge header file.

  5. Create a new Objective-C Methods (.m) file by going to File> New> File...(Command ?+ 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 .mfile" in the rest of the tutorial.

  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. 在您的桥接头文件中,添加行#import "MyObjectiveCHeaderFile.h",其中MyObjectiveCHeaderFile是您在第一步中创建的头文件的名称。因此,例如,如果您将头文件命名为InAppPurchase.h,则应将该行添加#import "InAppPurchase.h"到桥头文件中。

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

Coding

编码

Now we're going to get into the actual coding. Add the following code into your .hfile:

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

BOOL areAdsRemoved;

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

Next, you need to import the StoreKitframework into your .mfile, as well as add SKProductsRequestDelegateand SKPaymentTransactionObserverafter your @interfacedeclaration:

接下来,您需要将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

and now add the following into your .mfile, this part gets complicated, so I suggest that you read the comments in the code:

现在将以下内容添加到您的.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;
        }
    }
}

Now you want to add your code for what will happen when the user finishes the transaction, for this tutorial, we use removing adds, you will have to add your own code for what happens when the banner view loads.

现在你想添加你的代码来处理当用户完成交易时会发生什么,在本教程中,我们使用删除添加,你必须为横幅视图加载时发生的情况添加自己的代码。

- (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];
}

If you don't have ads in your application, you can use any other thing that you want. For example, we could make the color of the background blue. To do this we would want to use:

如果您的应用程序中没有广告,您可以使用任何其他您想要的东西。例如,我们可以将背景的颜色设为蓝色。为此,我们需要使用:

- (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];
}

Now, somewhere in your viewDidLoadmethod, you're going to want to add the following code:

现在,在您的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
}

Now that you have added all the code, go into your .xibor storyboardfile, and add two buttons, one saying purchase, and the other saying restore. Hook up the tapsRemoveAdsIBActionto the purchase button that you just made, and the restoreIBActionto the restore button. The restoreaction will check if the user has previously purchased the in-app purchase, and give them the in-app purchase for free if they do not already have it.

现在您已经添加了所有代码,进入您的.xibstoryboard文件,并添加两个按钮,一个表示购买,另一个表示恢复。将 连接tapsRemoveAdsIBAction到您刚刚制作的购买按钮,并将 连接restoreIBAction到恢复按钮。该restore操作将检查用户之前是否购买了应用内购买,如果他们还没有购买,则免费为他们提供应用内购买。

Submitting for review

提交审核

Next, go into App Store Connect, and click Users and Accessthen click the Sandbox Testersheader, and then click the +symbol on the left where it says Testers. You can just put in random things for the first and last name, and the e-mail does not have to be real - you just have to be able to remember it. Put in a password (which you will have to remember) and fill in the rest of the info. I would recommend that you make the Date of Birtha date that would make the user 18 or older. App Store TerritoryHASto be in the correct country. Next, log out of your existing iTunes account (you can log back in after this tutorial).

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

Now, run your application on your iOS device, if you try running it on the simulator, the purchase will alwayserror, you HAVE TOrun it on your iOS device. Once the app is running, tap the purchase button. When you are prompted to log into your iTunes account, log in as the test user that we just created. Next,when it asks you to confirm the purchase of 99¢ or whatever you set the price tier too, TAKE A SCREEN SNAPSHOT OF ITthis is what your going to use for your screenshot for reviewon App Store Connect. Now cancel the payment.

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

Now, go to App Store Connect, then go to My Apps> the app you have the In-app purchase on> In-App Purchases. Then click your in-app purchase and click edit under the in-app purchase details. Once you've done that, import the photo that you just took on your iPhone into your computer, and upload that as the screenshot for review, then, in review notes, put your TEST USERe-mail and password. This will help apple in the review process.

现在,转到App Store Connect,然后转到My Apps> the app you have the In-app purchase on> In-App Purchases。然后点击您的应用内购买,然后点击应用内购买详情下的编辑。完成后,将您刚在 iPhone 上拍摄的照片导入计算机,并将其作为屏幕截图上传以供审核,然后在审核笔记中输入您的测试用户电子邮件和密码。这将有助于苹果在过程中。

After you have done this, go back onto the application on your iOS device, still logged in as the test user account, and click the purchase button. This time, confirm the payment Don't worry, this will NOT charge your account ANY money, test user accounts get all in-app purchases for freeAfter you have confirmed the payment, make sure that what happens when the user buys your product actually happens. If it doesn't, then thats going to be an error with your doRemoveAdsmethod. Again, I recommend using changing the background to blue for testing the in-app purchase, this should not be your actual in-app purchase though. If everything works and you're good to go! Just make sure to include the in-app purchase in your new binary when you upload it to App Store Connect!

完成此操作后,返回 iOS 设备上的应用程序,仍以测试用户帐户身份登录,然后单击购买按钮。这一次,确认付款别担心,这不会向您的帐户收取任何费用,测试用户帐户免费获得所有应用内购买确认付款后,请确保用户实际购买您的产品时会发生什么发生。如果没有,那么这将是您的doRemoveAds方法错误。同样,我建议使用将背景更改为蓝色来测试应用内购买,但这不应该是您实际的应用内购买。如果一切正常,你就可以开始了!当您将其上传到 App Store Connect 时,请确保将应用内购买包含在您的新二进制文件中!



Here are some common errors:

以下是一些常见错误:

Logged:No Products Available

记录:No Products Available

This could mean four things:

这可能意味着四件事:

  • You didn't put the correct in-app purchase ID in your code (for the identifier kRemoveAdsProductIdentifierin the above code
  • You didn't clear your in-app purchase for sale on App Store Connect
  • You didn't wait for the in-app purchase ID to be registered in App Store Connect. Wait a couple hours from creating the ID, and your problem should be resolved.
  • You didn't complete filling your Agreements, Tax, and Banking info.
  • 您没有在代码中输入正确的应用内购买 ID(对于kRemoveAdsProductIdentifier上述代码中的标识符
  • 您未在App Store Connect上清除要出售的应用内购买
  • 您没有等待应用内购买 ID 在App Store Connect 中注册。创建 ID 后等待几个小时,您的问题应该会得到解决。
  • 您没有完成协议、税务和银行信息的填写。


If it doesn't work the first time, don't get frustrated! Don't give up! It took me about 5 hours straight before I could get this working, and about 10 hours searching for the right code! If you use the code above exactly, it should work fine. Feel free to comment if you have any questions at all.

如果它第一次不起作用,请不要沮丧!不要放弃!我花了大约 5 个小时才开始工作,大约 10 个小时才能找到正确的代码!如果您完全使用上面的代码,它应该可以正常工作。如果您有任何问题请随时发表评论。

I hope this helps to all of those hoping to add an in-app purchase to their iOS application. Cheers!

我希望这对所有希望在他们的 iOS 应用程序中添加应用程序内购买的人有所帮助。干杯!

回答by Yedidya Reiss

Just translate Jojodmo code to Swift:

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

        }


    }
}
} 

回答by Jojodmo

Swift Answer

快速回答

This is meant to supplement my Objective-C answerfor Swift users, to keep the Objective-C answer from getting too big.

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

Setup

设置

First, set up the in-app purchase on appstoreconnect.apple.com. Follow the beginning part of my Objective-C answer(steps 1-13, under the App Store Connectheader) for instructions on doing that.

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

It could take a few hours for your product ID to register in App Store Connect, so be patient.

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

Now that you've set up your in-app purchase information on App Store Connect, we need to add Apple's framework for in-app-purchases, StoreKit, to the app.

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

Go into your Xcode project, and go to the application manager (blue page-like icon at the top of the left bar where your app's files are). Click on your app under targets on the left (it should be the first option), then go to "Capabilities" at the top. On the list, you should see an option "In-App Purchase". Turn this capability ON, and Xcode will add StoreKitto your project.

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

Coding

编码

Now, we're going to start coding!

现在,我们要开始编码了!

First, make a new swift file that will manage all of your in-app-purchases. I'm going to call it IAPManager.swift.

首先,创建一个新的 swift 文件来管理您所有的应用内购买。我要打电话给它IAPManager.swift

In this file, we're going to create a new class, called IAPManagerthat is a SKProductsRequestDelegateand SKPaymentTransactionObserver. At the top, make sure you import Foundationand StoreKit

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

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

Next, we're going to add a variable to define the identifier for our in-app purchase (you could also use an enum, which would be easier to maintain if you have multiple IAPs).

接下来,我们将添加一个变量来定义我们的应用内购买的标识符(您也可以使用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"

Let's add an initializer for our class next:

接下来让我们为我们的类添加一个初始化程序:

// 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
}

Now, we're going to add the required functions for SKProductsRequestDelegateand SKPaymentTransactionObserverto work:

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

We'll add the RemoveAdsManagerclass later

我们稍后会添加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
        }
    }
}

Now let's add some functions that can be used to start a purchase or a restore purchases:

现在让我们添加一些可用于开始购买或恢复购买的功能:

// 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()
}

Next, let's add a new utilities class to manage our IAPs. All of this code could be in one class, but having it multiple makes it a little cleaner. I'm going to make a new class called RemoveAdsManager, and in it, put a few functions

接下来,让我们添加一个新的实用程序类来管理我们的 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()
}

The first three functions, removeAds, restoreRemoveAds, and areAdsRemoved, are functions that you'll call to do certain actions. The last four are one that will be called by IAPManager.

前三项功能,removeAdsrestoreRemoveAds,和areAdsRemoved,是函数,你会打电话做某些动作。最后四个是将被调用的一个IAPManager

Let's add some code to the first two functions, removeAdsand restoreRemoveAds:

让我们为前两个函数添加一些代码,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()
}

And lastly, let's add some code to the last five functions.

最后,让我们为最后五个函数添加一些代码。

// 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.
}

Putting it all together, we get something like this:

把它们放在一起,我们得到这样的东西:

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
            }
        }
    }

}

Lastly, you need to add some way for the user to start the purchase and call RemoveAdsManager.removeAds()and start a restore and call RemoveAdsManager.restoreRemoveAds(), like a button somewhere! Keep in mind that, per the App Store guidelines, you do need to provide a button to restore purchases somewhere.

最后,您需要为用户添加一些方式来开始购买和通话,RemoveAdsManager.removeAds()并开始恢复和通话RemoveAdsManager.restoreRemoveAds(),就像某处的按钮一样!请记住,根据 App Store 指南,您确实需要提供一个按钮以在某处恢复购买。

Submitting for review

提交审核

The last thing to do is submit your IAP for review on App Store Connect! For detailed instructions on doing that, you can follow the last part of my Objective-C answer, under the Submitting for reviewheader.

最后要做的就是在 App Store Connect 上提交您的 IAP 以供审核!有关这样做的详细说明,您可以按照我的 Objective-C 答案的最后一部分,在提交以供审核标题下。

回答by Vladimir Grigorov

RMStoreis a lightweight iOS library for In-App Purchases. It wraps StoreKit API and provides you with handy blocks for asynchronous requests. Purchasing a product is as easy as calling a single method.

RMStore是一个用于应用内购买的轻量级 iOS 库。它包装了 StoreKit API 并为您提供了用于异步请求的方便块。购买产品就像调用一个方法一样简单。

For the advanced users, this library also provides receipt verification, content downloads and transaction persistence.

对于高级用户,该库还提供收据验证、内容下载和交易持久化。

回答by Nirav Bhatt

I know I am quite late to post this, but I share similar experience when I learned the ropes of IAP model.

我知道我发布这个已经很晚了,但是当我学习 IAP 模型的绳索时,我分享了类似的经验。

In-app purchase is one of the most comprehensive workflow in iOS implemented by Storekit framework. The entire documentationis quite clear if you patience to read it, but is somewhat advanced in nature of technicality.

应用内购买是由 Storekit 框架实现的 iOS 中最全面的工作流程之一。在整个文档是很清楚,如果你耐心看完它,但在技术性的性质有所前进。

To summarize:

总结一下:

1 - Request the products - use SKProductRequest & SKProductRequestDelegate classes to issue request for Product IDs and receive them back from your own itunesconnect store.

1 - 请求产品 - 使用 SKProductRequest 和 SKProductRequestDelegate 类发出对产品 ID 的请求,并从您自己的 itunesconnect 商店接收它们。

These SKProducts should be used to populate your store UI which the user can use to buy a specific product.

这些 SKProducts 应该用于填充用户可以用来购买特定产品的商店 UI。

2 - Issue payment request - use SKPayment & SKPaymentQueue to add payment to the transaction queue.

2 - 发出付款请求 - 使用 SKPayment & SKPaymentQueue 将付款添加到交易队列中。

3 - Monitor transaction queue for status update - use SKPaymentTransactionObserver Protocol's updatedTransactions method to monitor status:

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 - Restore button flow - use SKPaymentQueue's restoreCompletedTransactions to accomplish this - step 3 will take care of the rest, along with SKPaymentTransactionObserver's following methods:

4 - 恢复按钮流程 - 使用 SKPaymentQueue 的 restoreCompletedTransactions 来完成此操作 - 步骤 3 将处理其余部分,以及 SKPaymentTransactionObserver 的以下方法:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Hereis a step by step tutorial (authored by me as a result of my own attempts to understand it) that explains it. At the end it also provides code sample that you can directly use.

是一个解释它的分步教程(由于我自己尝试理解它而由我编写)。最后还提供了可以直接使用的代码示例。

Hereis another one I created to explain certain things that only text could describe in better manner.

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