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

bvjveswy  于 2022-12-24  发布在  iOS
关注(0)|答案(5)|浏览(278)

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

这是一个关于如何将应用内购买添加到iOS应用的包罗万象的介绍

h9vpoimq

h9vpoimq1#

Swift用户

Swift用户可以 checkout My Swift Answer for this question
或者,看看Yedidya Reiss's Answer,它将这个Objective-C代码翻译成Swift。

Objective-C用户

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

应用程序商店连接

1.转到appstoreconnect.apple.com并登录
1.单击My Apps,然后单击要添加购买内容的应用程序
1.单击Features标题,然后选择左侧的In-App Purchases
1.单击中间的+图标
1.在本教程中,我们将添加一个应用内购买功能来移除广告,因此选择non-consumable。如果您要向用户发送实体物品,或者给予他们可以多次购买的物品,您将选择consumable
1.对于引用名称,输入您想要的任何内容(但要确保您知道它是什么)
1.对于产品id,输入tld.websitename.appname.referencename效果最好,例如,可以使用com.jojodmo.blix.removeads
1.选择cleared for sale,然后选择价格等级为1(99美分)。等级2将是1.99美元,等级3将是2.99美元。完整的列表是可用的,如果你点击view pricing matrix我建议你使用等级1,因为这通常是最高的任何人都会支付删除广告。
1.单击蓝色的add language按钮,输入信息。这些信息将全部显示给客户,因此不要放置任何您不希望他们看到的内容
1.对于hosting content with Apple,选择
1.您可以 * 暂时 * 将审核备注留空。
1.跳过screenshot for review * 暂时跳过 *,我们跳过的所有内容都会回来。
1.点击“保存”
App Store Connect中注册您的产品ID可能需要几个小时,所以请耐心等待。

设置项目

现在您已经在App Store Connect上设置了应用内购买信息,请进入Xcode项目,然后转到应用程序管理器(方法和头文件所在位置顶部的蓝色页面图标)单击目标下的应用程序(应该是第一个)然后去将军。在底部,你应该看到linked frameworks and libraries点击小加号并添加框架StoreKit.framework如果你不这样做,应用内购买将 * 不 * 工作!
如果您使用Objective-C作为应用的语言,您应该跳过这五个步骤。否则,如果您使用Swift,您可以遵循My Swift Answer for this question, here,或者,如果您更喜欢使用Objective-C作为应用内购买代码,但在应用中使用Swift,您可以执行以下操作:
1.通过FileNewFile...(Command + N)创建一个新的.h(头文件)文件。在本教程的其余部分,该文件将被称为“您的.h文件
1.出现提示时,单击创建桥接头。这将是我们的桥接头文件。如果没有提示,请转到步骤3。如果出现 * 提示,请跳过步骤3直接转到步骤4。
1.在主项目文件夹中创建另一个名为Bridge.h.h文件,然后转到应用程序管理器(蓝色页面状图标),在Targets部分选择您的应用程序,单击Build Settings,找到显示
Swift Compiler - Code Generation的选项,然后将Objective-C Bridging Header选项设置为Bridge.h
1.在桥接头文件中,添加行#import "MyObjectiveCHeaderFile.h",其中MyObjectiveCHeaderFile是您在步骤1中创建的头文件的名称。因此,例如,如果您将头文件命名为
InAppPurchase.h**,则应将行#import "InAppPurchase.h"添加到桥接头文件中。
1.创建新的Objective-C方法(.m)文件,方法是转到FileNewFile...(Command + N)。将其命名为与在步骤1中创建的头文件相同的名称。例如,如果在步骤1中将文件命名为 InAppPurchase.h,您可以将此新文件命名为 InAppPurchase.m。在本教程的其余部分中,此文件将称为“您的.m文件”。
编号
现在我们来看看实际的编码,将下面的代码添加到.h文件中:

BOOL areAdsRemoved;

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

接下来,需要将StoreKit框架导入到.m文件中,并在@interface声明之后添加SKProductsRequestDelegateSKPaymentTransactionObserver

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

现在,您希望添加代码,说明当用户完成事务时会发生什么,对于本教程,我们使用removing add,您必须添加自己的代码,说明当加载横幅视图时会发生什么。

- (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文件,添加两个按钮,一个说购买,另一个说恢复。将tapsRemoveAdsIBAction连接到你刚刚创建的购买按钮。restoreIBAction操作将检查用户之前是否购买了应用内购买,如果用户没有应用内购买,则免费给予应用内购买。

提交审核

接下来,进入App Store Connect,单击Users and Access,然后单击Sandbox Testers标题,再单击左侧的+符号,此处显示Testers。您可以在名和姓中输入随机值,电子邮件不一定是真实的的--你只需要记住它就行了。输入密码(你必须记住这一点)并填写其余的信息。我建议你将Date of Birth设置为一个日期,使用户达到18岁或更大。App Store Territory必须在正确的国家。下一步,注销现有的iTunes帐户(您可以在本教程结束后重新登录)。
现在,在iOS设备上运行您的应用程序,如果您尝试在模拟器上运行,购买将 * 总是 * 出错,您必须在iOS设备上运行它。应用程序运行后,点击购买按钮。当提示您登录iTunes帐户时,以我们刚刚创建的测试用户身份登录。接下来,当它要求你确认购买99美分或任何你设置的价格层太,采取屏幕截图它这是你要用你的screenshot for review在应用商店连接。现在取消付款。
现在,进入App Store Connect,然后进入My Appsthe app you have the In-app purchase onIn-App Purchases。然后点击你的应用内购买,点击应用内购买详情下的编辑。完成后,将你刚用iPhone拍的照片导入电脑,并上传为截图供审核,然后在审核注解中,输入您的测试用户电子邮件和密码。这将有助于苹果公司的审查过程。
完成此操作后,返回iOS设备上的应用程序,仍以测试用户帐户登录,然后单击购买按钮。这次,请确认付款别担心,这不会向您的帐户收取任何费用,测试用户帐户将免费获得所有应用程序内购买确认付款后,确保用户购买你的产品时发生的事情确实发生了。如果没有发生,那就是你的doRemoveAds方法出错了。同样,我建议在测试应用内购买时将背景改为蓝色。这不应该是你实际的应用内购买。如果一切正常,你可以去!只要确保包括应用内购买在您的新二进制文件时,你上传到应用商店连接!

以下是一些常见错误:

已记录:No Products Available

这可能意味着四件事:

  • 您没有在代码中输入正确的应用内购买ID(对于上面代码中的标识符kRemoveAdsProductIdentifier
  • 您尚未清除您在App Store Connect上的应用内购买
  • 您没有等待应用内购买ID在App Store Connect中注册。请在创建ID后等待几个小时,您的问题应该会得到解决。
  • 您未填写完协议、税务和银行信息。

如果第一次不成功,不要沮丧!不要给予!我花了大约5个小时才让它工作,花了大约10个小时寻找正确的代码!如果你完全使用上面的代码,它应该工作得很好。如果你有任何问题,请随时发表评论。

wlwcrazw

wlwcrazw2#

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

        }

    }
}
}
gcxthw6b

gcxthw6b3#

快速回答

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

设置

首先,在appstoreconnect.apple.com上设置应用内购买。请按照my Objective-C answer的开头部分(步骤1 - 13,位于App Store Connect标题下)获取相关说明。
您的产品ID可能需要几个小时才能在App Store Connect中注册,请耐心等待。
现在,您已在App Store Connect上设置了应用内购买信息,我们需要将Apple的应用内购买框架StoreKit添加到应用中。
进入你的Xcode项目,进入应用程序管理器(左栏顶部类似蓝色页面的图标,你的应用程序文件所在的位置)。点击左边目标下的应用程序(这应该是第一个选项),然后进入顶部的"功能"。在列表中,你应该看到一个选项"应用内购买"。打开这个功能,Xcode会将StoreKit添加到你的项目中。

编码

现在,我们要开始编码了!
首先,创建一个新的swift文件来管理你所有的应用内购买,我将它命名为IAPManager.swift
在此文件中,我们将创建一个名为IAPManager的新类,它是SKProductsRequestDelegateSKPaymentTransactionObserver

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

前三个函数removeAdsrestoreRemoveAdsareAdsRemoved是用来执行特定操作的函数,后四个函数将由IAPManager调用。
让我们为前两个函数removeAdsrestoreRemoveAds添加一些代码:

// 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以供审查!有关详细说明,请参阅my Objective-C answer的最后一部分,在提交审查标题下。

unguejic

unguejic4#

RMStore是一个轻量级的iOS应用内购买库。它封装了StoreKit API,并为您提供了方便的异步请求块。购买产品就像调用单个方法一样简单。
对于高级用户,该库还提供收据验证、内容下载和事务持久化。

2lpgd968

2lpgd9685#

我知道我很晚才发表这篇文章,但我分享了类似的经验,当我了解IAP模型的绳索。
应用内购买是Storekit框架在iOS中实现的最全面的工作流程之一,如果你耐心阅读entire documentation,它是相当清晰的,但在技术性上有些先进。
总结如下:
1-请求产品-使用SKProductRequest和SKProductRequestDelegate类发出产品ID请求,并从您自己的itunesconnect商店接收返回的产品ID。
这些SKProducts应用于填充商店UI,用户可以使用该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

Here是一个循序渐进的教程(由我自己编写,作为我自己尝试理解它的结果),它解释了它。在最后,它还提供了代码样本,你可以直接使用。
Here是我创建的另一个解释某些只有文本才能用更好的方式描述的事情的工具。

相关问题