苹果IAP总结

最近公司项目要增加内购功能,摸索了几天,遇到不少问题,先记录下来,有图有真相,我尽量多贴些图片。

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 收据信息是产品环境中使用,但却被发送到测试环境中验证

现在代码还比较散乱,只是实现了功能,后面准备封装一下,遇到问题再来补充吧~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • iOS应用内付费(IAP)开发步骤 1.苹果iTunes Connect内购产品信息录入。 1)创建app内购买项...
    MillerWang阅读 10,953评论 0 7
  • iOS支付 iOS支付分为两类,第三方支付和应用内支付(内购)。 第三方支付包括:支付宝支付、微信支付、银联支付、...
    羊驼先生丶阅读 898评论 0 0
  • 在我们应用开发中我们经常在自己的项目中使用到支付,下面我们来谈谈iOS这块的支付;iOS支付主要分为两类,第三方支...
    Hither阅读 8,254评论 9 42
  • 添加依赖
    仍是少年呀阅读 363评论 0 0