iOS的应用内购买 iAP 的一些问题
内购这块坑不算少,另外因为 sandbox 测试所需要特定的配置也很多,所以对于经验不太多的开发者来说很容易就遇到各种问题,并且测试时出错Apple给出的也只有“Can not connect iTunes Store”或者"Invalid Product IDs"之类毫无价值的错误提示,并没有详细的错误说明,因此调试起来往往没有方向。
再就是博主比较懒啦,就不扫盲了,部分步骤简略些。先申请 app 证书,必须支持推送,然后项目中打开推送。
- iTC中,新建一个 app,注意包名一定对应,再添加内购项目。
- 单纯测试的话,截图可以不传。
- 添加沙盒测试者 注意必须是未注册 Apple ID 的邮件地址,然后收到邮件激活即可
第三方库实现
下面列出来的库,一个都不好用,最终我用了 IAPHelper,自行 github 吧。
if(![IAPShare sharedHelper].iap) {
NSSet* dataSet = [[NSSet alloc] initWithObjects:@"eduadm_donate_3", @"eduadm_donate_6", @"eduadm_donate_12", @"eduadm_donate_25", nil];
[IAPShare sharedHelper].iap = [[IAPHelper alloc] initWithProductIdentifiers:dataSet];
}
[IAPShare sharedHelper].iap.production = NO;
[[IAPShare sharedHelper].iap requestProductsWithCompletion:^(SKProductsRequest *request, SKProductsResponse *response) {
if(response > 0 ) {
SKProduct* product =[[IAPShare sharedHelper].iap.products objectAtIndex:0];
[[IAPShare sharedHelper].iap buyProduct:product onCompletion:^(SKPaymentTransaction *transcation) {
if(transcation.error)
{
NSLog(@"Fail %@",[transcation.error localizedDescription]);
}
else if(transcation.transactionState == SKPaymentTransactionStatePurchased) {
[[IAPShare sharedHelper].iap checkReceipt:transcation.transactionReceipt AndSharedSecret:@"your sharesecret" onCompletion:^(NSString *response, NSError *error) {
//Convert JSON String to NSDictionary
NSDictionary* rec = [IAPShare toJSON:response];
if([rec[@"status"] integerValue]==0)
{
NSString *productIdentifier = transcation.payment.productIdentifier;
[[IAPShare sharedHelper].iap provideContent:productIdentifier];
NSLog(@"SUCCESS %@",response);
NSLog(@"Pruchases %@",[IAPShare sharedHelper].iap.purchasedProducts);
}
else {
NSLog(@"Fail");
}
}];
}
else if(transcation.transactionState == SKPaymentTransactionStateFailed) {
NSLog(@"Fail");
}
}];
}
}];
下面是结果:
2015-01-18 14:45:02.526 eduadmin[441:92076] SUCCESS {
"receipt":{"original_purchase_date_pst":"2015-01-17 22:44:51 America/Los_Angeles", "purchase_date_ms":"1421563491028", "unique_identifier":"25b170fda433c754e5ac3fc1a054a4b78560c716", "original_transaction_id":"1000000139233309", "bvrs":"62", "transaction_id":"1000000139233309", "quantity":"1", "unique_vendor_identifier":"9EE7D835-713A-4BDF-A2C0-50035D978ADE", "item_id":"959284801", "product_id":"eduadm_donate_12", "purchase_date":"2015-01-18 06:44:51 Etc/GMT", "original_purchase_date":"2015-01-18 06:44:51 Etc/GMT", "purchase_date_pst":"2015-01-17 22:44:51 America/Los_Angeles", "bid":"com.pupboss.eduadmin", "original_purchase_date_ms":"1421563491028"}, "status":0}
2015-01-18 14:45:02.527 eduadmin[441:92076] Pruchases {(
"eduadm_donate_12"
)}
不过还有个问题
直接
[[IAPShare sharedHelper].iap.products objectAtIndex:0]
你会不知道他顺序
加上这么一句
for (SKProduct *pro in [IAPShare sharedHelper].iap.products) {
NSLog(@"%@-----%@-----%@-----%@-----%@",pro.localizedDescription,pro.localizedTitle,pro.price,pro.priceLocale,pro.productIdentifier);
}
挨个打印出来,再做判断就好了
2015-01-18 17:43:30.568 eduadmin[521:120874] 大约是一个汉堡的价格-----自由捐赠-----12-----<__NSCFLocale: 0x1740e8f00>-----eduadm_donate_12
2015-01-18 17:43:30.571 eduadmin[521:120874] 大约是中杯星巴克的价格-----自由捐赠-----25-----<__NSCFLocale: 0x1740e8f00>-----eduadm_donate_25
2015-01-18 17:43:30.571 eduadmin[521:120874] 大约是一瓶可口可乐的价格-----自由捐赠-----3-----<__NSCFLocale: 0x1740e8f00>-----eduadm_donate_3
2015-01-18 17:43:30.572 eduadmin[521:120874] 大约是一碗麻辣烫的价格-----自由捐赠-----6-----<__NSCFLocale: 0x1740e8f00>-----eduadm_donate_6
下面介绍坑们
- 你是否在 iOS Dev Center中打开了对应应用AppID的
In-App Purchases
功能?登陆 iOS Dev Centre 的 Certificates, Identifiers & Profiles 下,在 Identifiers 中找到正在开发的 App,In-App Purchase 一项应当显示 Enabled(如果使用 Xcode5,可以直接在 Xcode 的 Capabilities 页面中打开 In-App Purchases)。 - 你是否在 iTunes Connect 中注册了你的 IAP 项目,并将其设为 Cleared for Sale?
- 你的 plist 中的
Bundle identifier
的内容是否和你的 AppID 一致? - 你是否正确填写了 Version(CFBundleVersion)和 Build(CFBuildNumber)两个数字?两者缺一不可。
- 你用代码向 Apple 申请售卖物品列表时是否使用了完整的在 iTC 注册的 Product ID?(使用在 IAP 管理中内购项目的 Product ID一栏中的字符串)
- 你是否在打开 IAP 以后重新生成过包含 IAP 许可的 provisioning profile?
- 你是否重新导入了新的包含 IAP 的 provisioning profile?建议在 Organiser 中先删掉原来设备上的老的 provisioning profile。
- 你是否在用包含 IAP 的 provisioning profile 在部署测试程序?在 Xcode5 中,建议使用 General 中的 Team 选项来自动管理。
- 你是否是在模拟器中测试 IAP?虽然理论上说模拟器在某些情况下可以测试 IAP,但是条件很多也不让人安心,因此你确实需要一台真机来做 IAP 测试。
- 你是在企业版发布中测试 IAP 么?因为企业版没有 iTC 进行内购项目管理,也无法发布 AppStore 应用,所以你在企业版的 build 中不能使用 IAP。
- 你是否将设备上原来的 app 删除了,并重新进行了安装?记得在安装前做一下 Clean 和 Clean Build Folder。
- 你是否在运行应用前将设备上实际的 Apple ID 登出了?建议在设置->iTunes Store 和 App Stroe 中将使用中的 Apple ID 登出,以未登录状态进入应用进行测试。
- 你是否使用的是 Test User?如果你还没有创建 Test User,你需要到 iTC 中创建。
- 你使用的测试账号是否是美国区账号?虽然不是一定需要,但是鉴于其他地区的测试账号经常抽风,加上美国区账号一直很稳定,因此强烈建议使用美国区账号。正常情况下 IAP 不需要进行信用卡绑定和其他信息填写,如果你遇到了这种情况,可以试试删除这个测试账号再新建一个其他地区的。
- 你是否有新建账户进行测试?可能的话,可以使用新建测试账户试试看,因为某些特定情况下测试账户会被 Apple 锁定。
- 你的应用是否是被拒状态(Rejected)或自己拒绝(Developer Rejected)了?被拒绝状态的应用的话对应还未通过的内购项目也会一起被拒,因此你需要重新将IAP项目设为 Cleared for Sale。
- 你的应用是否处于等待开发者发布(Pending Developer Release)状态?等待发布状态的 IAP 是无法测试的。
- 你的内购项目是否是最近才新建的,或者进行了更改?内购项目需要一段时间才能反应到所有服务器上,这个过程一般是一两小时,也可能再长一些达到若干小时。
- 你在 iTC 中 Contracts, Tax, and Banking Information 项目中是否有还没有设置或者过期了的项目?不完整的财务信息无法进行内购测试。
- 你是在越狱设备上进行内购测试么?越狱设备不能用于正常内购,你需要重装或者寻找一台没有越狱的设备。
- 你是否能正常连接到 Apple 的服务器,你可以访问 Apple 开发者论坛关于 IAP 的板块,如果苹果服务器正 down 掉,那里应该有热烈的讨论。
详细教程
- 苹果的官方 IAP 指南和相应的Technical Note
第三方库推荐
- mattt/CargoBay
- robotmedia/RMStore
- 当然了,这些库,坑比较多,也可以自己写
StoreKit 实现
上面写的比较乱,主要是之前的准备太繁琐,这次主要说 StoreKit 的代码。
IAPHelper
用法自行 Github
,这次我主要讲 StoreKit
的用法。
先打打预防针,我说下都有哪些坑
- 第三方库,无法准确的获取
已经购买的项目
。也可能是我用的姿势不对。 - 系统自带的,老特么闪退,概率在 1/5 ,点进去内购页,退出,点进去,退出,点进去,我擦,闪退了。
第三方库不说了, IAPHelper
用法自行 Github
,CargoBay
,文档写的那是个屁啊,鬼能看懂,RMStore
更蛋疼,导入项目,编译就报错,缺什么文件。
总之,我水平低,玩不了这么高级玩意儿。无奈来研究系统自带的了。
博主比较懒啦,能省的就省,至于怎么抽方法,你们自己看着来啦。
1.控制器中添加两个属性,一个代理协议一个观察者协议
@interface DonateTableViewController () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
{
NSArray *_productArr;
NSMutableArray *_purchasedArr;
}
2.添加观察者对象为自身
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
3.创建一个请求
if ([SKPaymentQueue canMakePayments]) {
// 没有意外
NSSet *set =[NSSet setWithArray:@[@"eduadm_donate_3", @"eduadm_donate_6", @"eduadm_donate_12", @"eduadm_donate_25"]];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
request.delegate = self;
[request start];
}else {
UIAlertView *view = [[UIAlertView alloc] initWithTitle:@"访问限制" message:@"您已经禁止应用内购买" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
[view show];
}
4.解析可购买产品数据
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
_productArr = response.products;
[MBProgressHUD hideHUD];
[self.tableView reloadData];
}
5.把产品添加到队列
SKPayment *payment = [SKPayment paymentWithProduct:product];
// 添加到队列
[[SKPaymentQueue defaultQueue] addPayment:payment];
6.监听
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *trans in transactions) {
switch (trans.transactionState) {
case SKPaymentTransactionStatePurchased:
[self completeTransaction:trans];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:trans];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:trans];
break;
default:
break;
}
}
}
7.监听的补充
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
// 处理 id
[self provideContentForProductIdentifier:transaction.payment.productIdentifier];
// 移除队列
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
[self provideContentForProductIdentifier:transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
if (transaction.error.code != SKErrorPaymentCancelled) {
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)provideContentForProductIdentifier:(NSString *)productIdentifier {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
[_purchasedArr addObject:productIdentifier];
[self.tableView reloadData];
}
The End
运行是没问题的,就是偶尔崩了,别找我,我不会