如何将应用内购买添加到 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
How do you add an in-app purchase to an iOS application?
提问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
应用商店连接
- Go to appstoreconnect.apple.comand log in
- Click
My Apps
then click the app you want do add the purchase to - Click the
Features
header, and then selectIn-App Purchases
on the left - Click the
+
icon in the middle - 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 chooseconsumable
. - For the reference name, put whatever you want (but make sure you know what it is)
- For product id put
tld.websitename.appname.referencename
this will work the best, so for example, you could usecom.jojodmo.blix.removeads
- Choose
cleared for sale
and 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 clickview pricing matrix
I recommend you use tier 1, because that's usually the most anyone will ever pay to remove ads. - Click the blue
add language
button, and input the information. This will ALL be shown to the customer, so don't put anything you don't want them seeing - For
hosting content with Apple
choose no - You can leave the review notes blank FOR NOW.
- Skip the
screenshot for review
FOR NOW, everything we skip we will come back to. - Click 'save'
- 前往appstoreconnect.apple.com并登录
- 单击
My Apps
然后单击您想要添加购买的应用程序 - 单击
Features
标题,然后In-App Purchases
在左侧选择 - 点击
+
中间的图标 - 在本教程中,我们将添加一个应用内购买来移除广告,所以选择
non-consumable
。如果您要向用户发送实物商品,或者给他们一些他们可以多次购买的商品,您会选择consumable
. - 对于参考名称,输入您想要的任何内容(但请确保您知道它是什么)
- 对于产品 id put
tld.websitename.appname.referencename
这将是最好的,例如,您可以使用com.jojodmo.blix.removeads
- 选择
cleared for sale
然后选择价格等级为 1 (99¢)。第 2 层为 1.99 美元,第 3 层为 2.99 美元。如果您单击view pricing matrix
我建议您使用第 1 层,则可以获得完整列表,因为这通常是任何人为删除广告而支付的最高费用。 - 点击蓝色
add language
按钮,输入信息。这将全部显示给客户,所以不要放任何你不想让他们看到的东西 - 对于
hosting content with Apple
选择否 - 您可以将复习笔记空白FOR NOW。
- 跳过
screenshot for review
FOR NOW,我们跳过的所有内容都会回来。 - 点击“保存”
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 libraries
click the little plus symbol and add the framework StoreKit.framework
If 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,则可以执行以下操作:
Create a new
.h
(header) file by going toFile
>New
>File...
(Command ?+ N). This file will be referred to as "Your.h
file" in the rest of the tutorialWhen 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.
Create another
.h
file namedBridge.h
in the main project folder, Then go to the Application Manager (the blue page-like icon), then select your app in theTargets
section, and clickBuild Settings
. Find the option that says Swift Compiler - Code Generation, and then set the Objective-C Bridging Headeroption toBridge.h
In your bridging header file, add the line
#import "MyObjectiveCHeaderFile.h"
, whereMyObjectiveCHeaderFile
is 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.Create a new Objective-C Methods (
.m
) file by going toFile
>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.m
file" in the rest of the tutorial.
.h
通过转到File
>New
>File...
( Command ?+ N)创建一个新的(头)文件。此文件将.h
在本教程的其余部分称为“您的文件”出现提示时,单击创建桥接头。这将是我们的桥接头文件。如果你不提示,请转到步骤3.如果您是提示,请跳过步骤3,直接进入第4步。
在主项目文件夹中创建另一个
.h
文件Bridge.h
,然后转到应用程序管理器(蓝色页面状图标),然后在该Targets
部分中选择您的应用程序,然后单击Build Settings
。找到Swift Compiler - Code Generation选项,然后将Objective-C Bridging Header选项设置为Bridge.h
在您的桥接头文件中,添加行
#import "MyObjectiveCHeaderFile.h"
,其中MyObjectiveCHeaderFile
是您在第一步中创建的头文件的名称。因此,例如,如果您将头文件命名为InAppPurchase.h,则应将该行添加#import "InAppPurchase.h"
到桥头文件中。.m
通过转到File
>New
>File...
( 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 .h
file:
现在我们将进入实际的编码。将以下代码添加到您的.h
文件中:
BOOL areAdsRemoved;
- (IBAction)restore;
- (IBAction)tapsRemoveAds;
Next, you need to import the StoreKit
framework into your .m
file, as well as add SKProductsRequestDelegate
and SKPaymentTransactionObserver
after your @interface
declaration:
接下来,您需要将StoreKit
框架导入到您的.m
文件中,SKProductsRequestDelegate
并SKPaymentTransactionObserver
在您的@interface
声明之后添加和:
#import <StoreKit/StoreKit.h>
//put the name of your view controller in place of MyViewController
@interface MyViewController() <SKProductsRequestDelegate, SKPaymentTransactionObserver>
@end
@implementation MyViewController //the name of your view controller (same as above)
//the code below will be added here
@end
and now add the following into your .m
file, 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 viewDidLoad
method, 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 .xib
or storyboard
file, and add two buttons, one saying purchase, and the other saying restore. Hook up the tapsRemoveAds
IBAction
to the purchase button that you just made, and the restore
IBAction
to the restore button. The restore
action 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.
现在您已经添加了所有代码,进入您的.xib
或storyboard
文件,并添加两个按钮,一个表示购买,另一个表示恢复。将 连接tapsRemoveAds
IBAction
到您刚刚制作的购买按钮,并将 连接restore
IBAction
到恢复按钮。该restore
操作将检查用户之前是否购买了应用内购买,如果他们还没有购买,则免费为他们提供应用内购买。
Submitting for review
提交审核
Next, go into App Store Connect, and click Users and Access
then click the Sandbox Testers
header, 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 Birth
a date that would make the user 18 or older. App Store Territory
HASto 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 Territory
HAS是在正确的国家。接下来,注销您现有的 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 review
on 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 doRemoveAds
method. 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
kRemoveAdsProductIdentifier
in 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 StoreKit
to 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 IAPManager
that is a SKProductsRequestDelegate
and SKPaymentTransactionObserver
. At the top, make sure you import Foundation
and StoreKit
在这个文件中,我们将创建一个新类,称为IAPManager
a SKProductsRequestDelegate
and SKPaymentTransactionObserver
。在顶部,确保您导入Foundation
并StoreKit
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 SKProductsRequestDelegate
and SKPaymentTransactionObserver
to work:
现在,我们将添加所需的功能SKProductsRequestDelegate
并SKPaymentTransactionObserver
使其工作:
We'll add the RemoveAdsManager
class 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
.
前三项功能,removeAds
,restoreRemoveAds
,和areAdsRemoved
,是函数,你会打电话做某些动作。最后四个是将被调用的一个IAPManager
。
Let's add some code to the first two functions, removeAds
and 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.
这是我创建的另一个内容,用于解释某些只有文字才能更好地描述的事物。