最近公司项目要增加内购功能,摸索了几天,遇到不少问题,先记录下来,有图有真相,我尽量多贴些图片。
1、首先你要有一个app,无论上架与否都可以,如果没有,就新建一个,怎么新建APP就不多说了,有一条要注意,APP的APPID信息里一定要支持In-App Purchases。
2、进入iTunes Connect 选择我的APP,在APP的详情页,选择功能选项,添加APP内要出售的物品
添加物品的时候会让你选择物品类型,按你的需求选择就行了,有几个商品就添加几个,
注意,物品的资料要填写完整,必填项一定要填,
审核信息里的图片要上传,备注也要写上,
3、当你的物品状态是这样的,就说明可以在代码里获取物品了,
当然,这时候你要直接写代码获取,肯定是获取不到的,因为还有一些非APP信息需要补充,想当初我就是没逼着产品完善信息,自己一直找错误,浪费了许多时间。
协议、税务和银行业务,这里面的信息怎么填写,这篇文章写的很详细,就不多说了,让产品参考着写吧。
4、为了测试支付情况,我们还需要新建一个测试用的Apple id,在iTunes Connect里选择用户和职能
选择沙箱技术测试员,添加测试账号,可以参考这篇文章。
5,以上都完成之后,终于可以打开编译器了,对,打开xcode,
打开Xcode 的这个功能。
6,写代码吧,
引入#import <StoreKit/StoreKit.h>
遵守协议
添加监听
[[SKPaymentQueue defaultQueue]addTransactionObserver:self];
(别忘了移除监听)
请求物品列表可以从自己的服务器,也可以从苹果的服务器请求,但是从苹果请求的话,慢慢等吧,很慢的,而且不稳定。
下面的代码是从点击物品开始,直到完成付款并通过自己的服务器验证。
//点击购买按钮- (void)clickPurcaseBtnAction{
//点击按钮的时候判断app是否允许apple支付
if ([SKPaymentQueue canMakePayments]) {
//请求苹果后台商品
[self requestProductData:_productId];
}
else {
NSLog(@"用户不允许应用内购买");
}}
//去苹果服务器请求商品
-(void)requestProductData:(NSString *)type {
//根据商品ID查找商品信息
NSArray *product = [[NSArray alloc] initWithObjects:type, nil];
NSSet *nsset = [NSSet setWithArray:product];
//创建SKProductsRequest对象,用想要出售的商品的标识来初始化,然后附加上对应的委托对象。
//该请求的响应包含了可用商品的本地化信息。
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];
}
// SKProductsRequestDelegate
//查询成功的回调
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse: (SKProductsResponse *)response {
NSArray *product = response.products;
//invalidProductIdentifiers是不被App Store所识别的产品id字符串数组,通常为空
NSLog(@"产品Product ID:%@",response.invalidProductIdentifiers);
//如果没应用的银行信息没填写完整,你添加的商品会出现在无效商品列表里
NSLog(@"无效商品列表 :%@",response.invalidProductIdentifiers);
if(product == nil) {
return;
}
//数组的count代表回调的产品ID数组的长度
if (product.count == 0) {
NSLog(@"无法获得产品信息,购买失败");
return;
}
//SKProduct对象包含了在App Store上注册的商品的本地化信息。
SKProduct *storeProduct = nil;
for (SKProduct *pro in product) {
if ([pro.productIdentifier isEqualToString:_productId]) {
storeProduct = pro;
}
}
if(storeProduct == nil) {
return;
}
//创建一个支付对象,并放到队列中
self.g_payment = [SKMutablePayment paymentWithProduct:storeProduct];
//设置购买的数量
self.g_payment.quantity = 1;
[[SKPaymentQueue defaultQueue] addPayment:self.g_payment];
}
//查询失败的回调
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(@"请求商品失败%@", error);
}
//交易有结果时会调用此回调函数,SKPaymentTransactionObserver - 交易的回调
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray*)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing: //正在购买
break;
case SKPaymentTransactionStatePurchased: //购买成功
[self completedTransaction:transaction Product:self.productId];
break;
case SKPaymentTransactionStateFailed://购买失败
{
UIAlertView *alt3 = [[UIAlertView alloc]initWithTitle:@"提示" message:@"购买失败" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
[alt3 show];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
break;
case SKPaymentTransactionStateRestored: //恢复购买
{
//当上次购买过程中,因某种原因以外中断而没能调用完成购买的API时,再次进入APP苹果会提醒恢复购买,可以在这里继续处理
}
break;
case SKPaymentTransactionStateDeferred: //最终状态未确认
{
UIAlertView *alt5 = [[UIAlertView alloc]initWithTitle:@"提示" message:@"无法获得产品信息,购买失败" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
[alt5 show];
}
break;
default:
break;
}
}
}
-(void)completedTransaction:(SKPaymentTransaction*)transaction Product:(NSString *)productId{
//NSString * productIdentifier = transaction.payment.productIdentifier;
NSString *transactionReceiptString= nil;
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
把transactionReceiptString发送给自己的服务器让后台验证就行了,还有一点就是对于sandbox环境和生产环境,校验的地址是不同的,后台校验的时候要区分测试和生产环境,注意:提交苹果审核后,审核的时候苹果用的也是测试环境,后台去验证的时候需要在测试环境验证,过审后再去生产环境验证。
https://buy.itunes.apple.com/verifyReceipt (生产)
https://sandbox.itunes.apple.com/verifyReceipt (sandbox测试)
/* 下面的代码是客户端自己验证的方法
NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", transactionReceiptString];//拼接请求数据
NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
NSURL *url=[NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url];
requestM.HTTPBody = bodyData;
requestM.HTTPMethod=@"POST";
//创建连接并发送同步请求
NSError *error=nil;
NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error];
if (error) {
NSLog(@"验证购买过程中发生错误,错误信息:%@",error.localizedDescription);
return;
}
NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
NSLog(@"%@",dic);
if([dic[@"status"] intValue]==0){
[[UIApplication sharedApplication].keyWindow makeToast:@"购买成功" duration:1.5 position:CSToastPositionCenter];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSDictionary *dicReceipt= dic[@"receipt"];
NSDictionary *dicInApp=[dicReceipt[@"in_app"] firstObject];
NSString *productIdentifier= dicInApp[@"product_id"];//读取产品标识
//如果是消耗品则记录购买数量,非消耗品则记录是否购买过
//NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
if ([productIdentifier isEqualToString:@"123"]) {
// NSInteger purchasedCount=[defaults integerForKey:productIdentifier];//已购买数量
// [[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier];
}else{
// [defaults setBool:YES forKey:productIdentifier];
}
//在此处对购买记录进行存储,可以存储到开发商的服务器端
}else{
NSLog(@"购买失败,未通过验证");
NSString *str = [NSString stringWithFormat:@"失败了%@",dic[@"status"]];
[[UIApplication sharedApplication].keyWindow makeToast:str duration:1.5 position:CSToastPositionCenter];
}
*/
}
遇到的问题
1、银行信息不完整造成的无法获取商品列表
2、苹果服务器慢,网络问题等造成的购买失败
3、交给服务器验证的字符串,服务器需要再进行UTF8编码一次再去验证,否则会返回21002,收据数据不符合格式。
4、顺便注上苹果反馈的错误码:
21000App Store无法读取你提供的JSON数据
21002 收据数据不符合格式
21003 收据无法被验证
21004 你提供的共享密钥和账户的共享密钥不一致
21005 收据服务器当前不可用
21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
现在代码还比较散乱,只是实现了功能,后面准备封装一下,遇到问题再来补充吧~