138-0432-1111
中文完整介绍:
https://blog.csdn.net/jjjjjj123321/article/details/78481248
开发文档:
https://developer.apple.com/in-app-purchase/
配置文档:
https://help.apple.com/itunes-connect/developer/#/dev1f52cd4dd?sub=dev456315af2
注意事项:
https://developer.apple.com/library/content/technotes/tn2259/_index.html
一 四种应用内支付类型:
1 Consumable
消耗型的,越用越少,可反复购买,比如游戏中的水晶。
2 Non-Consumable
一次性购买,不会过期,比如P图app的滤镜。
3 Auto-Renewable Subscriptions
用户可以购买服务的访问权限或者周期性更新的内容。比如按月访问云存储,或者订阅周刊。用户会定期付费,直到他们决定取消订阅。看意思是自动续费,类似于腾讯视频会员的自动续费。
4 Non-Renewing Subscriptions
Users can purchase access to services or content for a limited duration, such as a season pass to streaming content. This type of subscription does not renew automatically, so users need to renew each time.
用户可以购买服务或者内容的访问权限,这个是有时间限制的。比如腾讯视频的一个月的会员,这个订阅不会自动更新(自动续费),用户需要主动更新(主动付费)
二 准备工作:
1 签署Paid Applications Agreement协议,设置银行账户和税费信息
地址:https://help.apple.com/itunes-connect/developer/#/devb6df5ee51
2 配置Xcode
地址:http://help.apple.com/xcode/mac/current/#/dev88ff319e7
3 在iTunes Connect上创建In-App Purchases
include details such as name, pricing, and description that highlights the features and functionality of your in-app purchase.
地址:https://help.apple.com/itunes-connect/developer/#/devae49fb316
三 设计&开发:
1 设计你的IAP商店
设计指导:https://developer.apple.com/videos/play/wwdc2014/218/
优化指导:https://developer.apple.com/videos/play/wwdc2014/303/
2 实现IAP
通过StoreKit Framework将IAP商店嵌入你的app,保证内容和服务的购买过程安全可靠。
编程指导:
该框架会连接App Store,安全地处理用户的支付,提醒他们授权支付。之后该框架会通知你的app,提供对应的产品给用户。使用IAP来收集 支付及支付以外的特性和内容。
使用IAP你可以实现以下场景:
一个基本的app版本,提供了额外的高级特性
一个杂志app,允许用户购买和下载新的期刊
一个游戏提供新的难度供玩家探索
一个在线游戏,允许用户购买虚拟物品
用户浏览的你app的商店,你的app展示出示的产品。
用户选择想要购买的产品,app从App Store请求支付。
App Store处理支付请求,你的app提供用户购买的产品。
[1] 获取产品id
在你app上出售的每一个产品都有一个唯一的id。你的app使用这些产品id来从app store获取产品信息,比如价格,当用户购买这些产品时,也是使用这个id来提交支付请求的。app可以通过app bundle中存储一份id列表,也可以从服务器获取id列表(要考虑版本兼容性问题,最好带上支持的最低版本号)。【简单来说,就是id要从自己服务器获取,要注意版本兼容性问题】
[2] 获取产品信息
为了确保你的用户看到的都是实际可购买的产品,在展示你的商店之前,先去app store上查一下产品信息。
Use a products request object to query the App Store
First, create an instance of SKProductsRequest and initialize it with a list of product identifiers.
Be sure to keep a strong reference to the request object; otherwise, the system might deallocate the request before it can complete.
(1) 创建并初始化一个SKProductsRequest,确保对请求对象的强引用,否则,系统可能在请求完成前销毁它。
(2) 这个请求获取产品的正确信息,还有无效产品的id列表。并调用delegate来处理这个结果。
(3) delegate必须实现SKProductsRequestDelegate协议,来处理 App Store返回的响应。
代码:
- (void)validateProductIdentifiers:(NSArray *)productIdentifiers
{
SKProductsRequest *productsRequest = [[SKProductsRequest alloc]
initWithProductIdentifiers:[NSSet setWithArray:productIdentifiers]];
// Keep a strong reference to the request.
self.request = productsRequest;
productsRequest.delegate = self;
[productsRequest start];
}
// SKProductsRequestDelegate protocol method
- (void)productsRequest:(SKProductsRequest *)request
didReceiveResponse:(SKProductsResponse *)response
{
self.products = response.products; // 产品对象列表
for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
// Handle any invalid product identifiers.
}
[self displayStoreUI]; // Custom method
}
当用户购买了产品,你需要根据相应的产品对象创建一个支付请求,所以要把返回给delegate的产品对象列表保存下来。如果你app上卖的产品会发生改变,你可能需要创建一个类,来封装对产品对象的引用,以及其他信息(比如:你从服务器获取的图片和描述文本)。收到无效的产品id,通常说明你app的产品id列表存在错误,很有可能是iTunes Connect上配置不正确导致的。
[3] 展示app的商店UI
只有用户可以支付的时候才显示iap商店,价格要明确,以下是格式化显示价格的方法:
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setLocale:product.priceLocale];
NSString *formattedPrice = [numberFormatter stringFromNumber:product.price];
[4] 请求支付
(1) 创建一个支付请求
当用户选择购买一个产品,利用产品对象创建一个支付请求,可以设置购买数量。
SKProduct *product = <# Product returned by a products request #>;
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
payment.quantity = 2;
(2) 防欺诈
app store使用irregular activity detection engine来检测欺诈行为。一些app可以提供额外的信息来提高引擎对不正常交易的探测能力。If your users have an account with you, in addition to their App Store accounts, provide this additional piece of information when requesting payment.
例如,考虑以下两种情况。正常情况下,服务器上的不同用户,使用各自不同的app store账号来购买游戏金币。相反,同一个用户使用不同的app store账号来多次购买金币,app store是无法探测到这种不正常的情况的,app store需要你提供该用户在你服务器上的账号。通过以下代码获得用户账号的哈希值,然后赋值给applicationUsername属性。
import <CommonCrypto/CommonCrypto.h>
// Custom method to calculate the SHA-256 hash using Common Crypto
- (NSString )hashedValueForAccountName:(NSString)userAccountName
{
const int HASH_SIZE = 32;
unsigned char hashedChars[HASH_SIZE];
const char *accountName = [userAccountName UTF8String];
size_t accountNameLen = strlen(accountName);
// Confirm that the length of the user name is small enough
// to be recast when calling the hash function.
if (accountNameLen > UINT32_MAX) {
NSLog(@"Account name too long to hash: %@", userAccountName);
return nil;
}
CC_SHA256(accountName, (CC_LONG)accountNameLen, hashedChars);
// Convert the array of bytes into a string showing its hex representation.
NSMutableString *userAccountHash = [[NSMutableString alloc] init];
for (int i = 0; i < HASH_SIZE; i++) {
// Add a dash every four bytes, for readability.
if (i != 0 && i%4 == 0) {
[userAccountHash appendString:@"-"];
}
[userAccountHash appendFormat:@"%02x", hashedChars[i]];
}
return userAccountHash;
}
(3) 提交支付请求
Adding a payment request to the transaction queue submits it to the App Store. If you add a payment object to the queue multiple times, it’s submitted multiple times—the user is charged multiple times and your app is expected to deliver the product multiple times.
通过将支付请求添加到事务队列来将其提交到app store。如果你多次增加一个支付对象到队列,就会提交多次,用户就会消费多次,你的产品,讲道理,就应该给用户提供多次产品。所以要锁UI,或者锁住按钮。
[[SKPaymentQueue defaultQueue] addPayment:payment];
[5] 等待app store,处理事务
你的app提交的每一个支付请求,都有一个必须处理的事务。
在你的app与app store之间通信的场景中,事务队列扮演了一个核心角色。当事务的状态发生变化时,比如,支付成功,StoreKit会调用事务队列的观察者。哪个类做为观察者,由你来决定。观察者必须遵循 SKPaymentTransactionObserver协议。你的app不必轮询事务的状态。除了支付以外,你还可以通过事务队列下载Apple-hosted(托管?) content and to find out that subscriptions have been renewed。
当你的app启动时,注册一个事务队列观察者,确保观察者可以在任何时候处理事务。比如,考虑这种情况:用户在进入隧道之前,正在你的app中购买某些东西。你的app没有为用户提供购买的内容,因为没有网络连接。下一次你的app启动,StoreKit会再次调用你的事务队列观察者,你需要在此时为用户提供购买的内容。类似的,如果你的app没有成功的标记一个事务已经处理完成,StoreKit会在每次你的app启动时,调用观察者,直到事务正确的处理完成
// app启动时,添加观察者
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
/* ... */
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
}
在观察者类中实现 paymentQueue:updatedTransactions: 方法,StoreKit会在事务状态发生变化时,调用该方法,比如支付完成。事务的状态告诉你,你的app需要如何处理。
- (void)paymentQueue:(SKPaymentQueue *)queue
updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
// Call the appropriate custom method for the transaction state.
case SKPaymentTransactionStatePurchasing: // 处理中
[self showTransactionAsInProgress:transaction deferred:NO];
break;
case SKPaymentTransactionStateDeferred: // 延期
[self showTransactionAsInProgress:transaction deferred:YES];
break;
case SKPaymentTransactionStateFailed: // 失败
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStatePurchased: // 购买成功
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateRestored: // 已经购买过了
[self restoreTransaction:transaction];
break;
default:
// For debugging
NSLog(@"Unexpected transaction state %@", @(transaction.transactionState));
break;
}
}
}
[6] 存储购买记录
购买完成后,你的app需要保存一个持久化的购买记录。你的app用户使用该购买记录,在app启动后继续使用购买到的产品。这个记录还要用来恢复购买。具体见Restoring Purchased Products。你的app的持久化策略依赖于产品的类型和iOS版本。
For non-consumable products and auto-renewable subscriptions in iOS 7 and later, use the app receipt as your persistent record.
For non-renewing subscriptions, use iCloud or your own server to keep a persistent record.
For consumable products, your app updates its internal state to reflect the purchase, but there’s no need to keep a persistent record because consumable products aren’t restored or synced across devices. Ensure that the updated state is part of an object that supports state preservation (in iOS) or that you manually preserve the state across app launches (in iOS or macOS). For information about state preservation, see State Preservation and Restoration.
使用app收据来保存。
app收据包含了用户的购买记录,该记录由苹果加密。 Receipt Validation Programming Guide
消费型产品的信息在他们被支付后,会被加入到收据中,并且会一直保存在该收据中,直到你完成事务。在你完成事务后,这一信息会在下一次更新收据时,被删除,比如,下次用户有购买行为时。其他类型的产品会在支付时生成收据,并且会一直保存在收据中。
Persisting a Value in User Defaults or iCloud
if USE_ICLOUD_STORAGE
NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
else
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
endif
[storage setBool:YES forKey:@"enable_rocket_car"];
[storage setObject:@15 forKey:@"highest_unlocked_level"];
[storage synchronize];
Persisting a Receipt in User Defaults or iCloud
if USE_ICLOUD_STORAGE
NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
else
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
endif
NSData *newReceipt = transaction.transactionReceipt;
NSArray *savedReceipts = [storage arrayForKey:@"receipts"];
if (!savedReceipts) {
// Storing the first receipt
[storage setObject:@[newReceipt] forKey:@"receipts"];
} else {
// Adding another receipt
NSArray *updatedReceipts = [savedReceipts arrayByAddingObject:newReceipt];
[storage setObject:updatedReceipts forKey:@"receipts"];
}
[storage synchronize];
Persisting Using Your Own Server
Send a copy of the receipt to your server along with some kind of credentials or identifier so you can keep track of which receipts belong to a particular user. For example, let users identify themselves to your server with an email or user name, plus a password. Don’t use the identifierForVendor property of UIDevice—you can’t use it to identify and restore purchases made by the same user on a different device, because different devices have different values for this property.
[7] 解锁app功能
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
// Custom method to work with receipts
BOOL rocketCarEnabled = [self receipt:receiptData
includesProductID:@"com.example.rocketCar"];
[8] 完成事务
Finishing a transaction tells StoreKit that you’ve completed everything needed for the purchase
Unfinished transactions remain in the queue until they’re finished, and the transaction queue observer is called every time your app is launched so your app can finish the transactions.
Your app needs to finish every transaction, regardless of whether the transaction succeeded or failed.
Complete all of the following actions before you finish the transaction:
- Persist the purchase.
- Download associated content.
- Update your app’s UI to let the user access the product.
To finish a transaction, call the finishTransaction: method on the payment queue.
SKPaymentTransaction *transaction = <# The current payment #>;
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
After you finish a transaction, don’t take any actions on that transaction or do any work to deliver the product. If any work remains, your app isn’t ready to finish the transaction yet.
api文档:
https://developer.apple.com/documentation/storekit
3 验证收据:
Receipts provide a valuable record of the sale. Consider using receipt validation code to protect your content and prevent unauthorized purchases.
收据提供了一个有价值的销售记录。考虑使用收据验证代码来保护您的内容,防止未授权的购买。
编程指导:
四 测试
1 交易测试
使用沙箱测试环境来测试IAP,不会发生真实的消费。
创建一个沙箱测试账号:http://help.apple.com/itunes-connect/developer/#/dev8b997bee1
测试步骤:
2 完整用户体验测试
在App Store上发布您的应用程序之前,使用TestFlight可以从更广泛的用户那里获取有关您的应用和应用内购买的宝贵反馈。在iTunes Connect中邀请您的团队中的用户,可以通过电子邮件,增加10000名测试人员,所有应用程序内购买在beta测试期间是免费的,并且在测试期结束时不会继续。
https://developer.apple.com/testflight/
3 推荐的测试步骤
(1) 在iTunes Connect上创建一个测试账号
(2) 在一台调试手机上,登出app store。然后编译运行你的app
(3) 使用你的app来产生一个iap。当提示要登录app store的时候,使用你的测试账号。注意文案“[Environment: Sandbox]”是提示的一部分,表明你连的是测试环境
(4) 如果没有[Environment: Sandbox],那么你连的是生产环境。
注意,不要用测试账号登录生产环境,如果你这样做,测试账号就废了,再也不能用了。
http://blog.csdn.net/xiaominghimi/article/details/6937097
坑:
五 发布
1 提交审核
2 在App Store上推广您的IAP
在iOS11上,您可以在您的产品页面,推广20个IAP产品。
https://developer.apple.com/app-store/promoting-in-app-purchases/
3 发布促销代码
这个就是试用代码,每个app可以分发1000个code
疑问:
放到商店里的产品是怎么定义的?是否可以在线动态调整?还是只能通过iTunes Connect配置?
整不明白,就到这里提问:
https://developer.apple.com//contact/app-store/
中文资料:
http://blog.csdn.net/darling_shadow/article/details/51538267
itunes connect填写银行等信息:
http://www.jianshu.com/p/c7cf65911bc1
这个一定要看,最完整阐述:
http://blog.csdn.net/teng_ontheway/article/details/47011813
http://openfibers.github.io/blog/2015/02/28/in-app-purchase-walk-through/
http://blog.csdn.net/teng_ontheway/article/details/47016373
http://blog.csdn.net/teng_ontheway/article/details/47023119
第三方库: