详解iOS应用程序内使用IAP/StoreKit付费、沙盒(SandBox)测试、创建测试账号流程!

转自:http://blog.csdn.net/xiaominghimi/article/details/6937097

参考:http://mobile.51cto.com/hot-410094_all.htm


//——2012-12-11日更新   获取"产品付费数量等于0这个问题"的原因

看到很多童鞋问到,为什么每次都返回数量等于0??

其实有童鞋已经找到原因了,原因是你在 ItunesConnect 里的 “Contracts, Tax, and Banking ”没有完成设置账户信息。

确定 ItunesConnect 里 “Contracts, Tax, and Banking ”的状态,如下图所示,即可:


这里也是由于Himi疏忽的原因没有说明,这里先给童鞋们带来的麻烦,致以歉意。


//——2012-6-25日更新iap恢复

看到很多童鞋说让Himi讲解如何恢复iap产品,其实博文已经给出了。这里再详细说下:

首先向AppStore请求恢复交易:

1
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

然后当用户输入正确的appStore账号密码后,进入

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions//交易结果

进入上面函数中的

1
2
3
4
5
6
case SKPaymentTransactionStateRestored: //恢复
             {
                 [self restoreTransaction:transaction];
 
             }
                 break ;

然后我们再以下重写函数中处理即可!

- (void) restoreTransaction: (SKPaymentTransaction *)transaction

//——-



       终于在11月公司的游戏即将上线了,那么对于iOS游戏来说当今都是内置道具收费属于主流,那么我们的游戏也是内置收费,所以Himi这里分享给大家关于内置应用收费以及申请测试账号进行测试购买的经验;

      在应用内嵌入付费代码这一快Himi可以直接将代码分享给大家,所以我们来说一些主要流程,毕竟没有接触过这一块的童鞋肯定相当头疼 =。  =

     OK,步入整体,如果你想在iOS里内嵌收费,那么分为以下几步:

            

             【提示:以下创建App部分内容,你不用非要等项目能打包了才开始做,可以随时并且随便的创建个测试项目即可,因为嵌入付费并不要求上传App的ipa包的!!】          

 

     第一步:你需要在iTunesConnect中创建个新的App,然后为这个App设置一些产品(付费道具)等;

     OK,这里Himi稍微解释下,iTunesConnect是苹果提供的一个平台,主要提供AP发布和管理App的,最重要的功能是创建管理项目信息,项目付费产品(道具)管理、付费的测试账号、提交App等等,这里就简单介绍这么多,关于产品一词在此我们可以理解成游戏道具即可;在苹果看来所有付费都属于产品 =。 =千万不要纠结字眼哦~

    OK,打开iTunesConnect网站:https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa (注意:企业级的用户必须使用公司主开发者账号登陆才可!)

    成功登陆后的页面如下:


              

              这里大概说下重要的一些项:

             Contracts, Tax, and Banking   : 管理银行账号、联系人以及税等等;这里要根据提示完成对应的信息填写!一定要详细填写喔~

             Manage Users :管理用户的,比如主账号以及测试付费的(测试App)账号;

             Manage Your Applictions:管理应用程序的,你所有发布的应用和每个应用的状态都在这里面;


     下面我们新建一个App项目,大家放心,我们这里创建的是不会直接提交给App审核的,所以放心创建,只要控制好App的状态不要是待审核状态即可,不过即使你不小心将项目提交了,也没事,直接更改App状态即可了;

     选择Manage Your Applictions选项,然后新建一个项目:【Add New App】,根据提示来填写吧,这里就不细致说明了~

     创建好一个App之后,在点击Manage Your Applictions后的界面应该如下:

     

    这里你将看到自己创建的App,点击你创建的App项目,这里Himi创建的项目名字叫”ProjectForBuyTest“,点击你的App进入如下界面:



    

  (注意:这里的Bundle ID一定要跟你的项目中的info.plist中的Bundle ID保证一致!!!!)

     这里可以管理你的项目的信息、状态、是否嵌入GameCenter等等选项,那么本章我们重点介绍如何使用IAp沙盒测试程序内付费,所以这里我们点击右上角的”Manage In-App Purchases“选项进入创建产品(游戏道具)界面如下:



      上图中的下方看到Himi创建过的四个产品(道具)了,你可以点击”Create New“选项新建一个产品(付费道具),点击新建如下界面:


  

   上图中Himi没有截图出所有的选项,这里大概介绍下,这个界面是选择你的消费道具的种类,种类说明如下:

   类型选择有四种选择:

   1.Consumable(消耗品): 每次下载都需要付费;

   2.Non-consumable(非消耗品): 仅需付费一次;

   3.Auto-Renewable Subscriptions:自动订阅;

   4.Free Subscription:免费订阅

   最下方是你沙盒测试的截图,暂且不管即可;

   这里Himi选择Consumable选项,比如很多游戏都是购买金币啦这样子就可以选择这个;然后出现如下界面:



   Reference Name: 付费产品(道具的)参考名称

   Product ID(产品ID): 你产品的唯一id。通常格式是 com.xx.yy,但它可以是任何形式,不要求以程序的App ID作为前缀。

   Add Language: 添加产品名称与描述语言;

   Price Tier:选择价格,这里你选择价格后,会出现如上图最下方的价格对照表

   Screenshot(截屏): 展示你产品的截屏。(这个直接无视,测试App务必要管这个的)


  Product ID(产品ID)可以创建多个,比如我想游戏中分为0.99$ 、1.99$等道具那就创建对应多个产品ID

  我们填写好了”Reference Name“与”Product ID“以及”Price Tier“后,点击”Add Language“选项然后出现如下界面:

            


  上图中的选项:

      Language:语言

      Displayed Name(显示名称): 用户看到的产品名称。

      Description(描述): 对产品进行描述。

  

  Ok,一路 Save保存回到”Manage In-App Purchases“界面中会看到我们新建的产品(道具)如下:

 


  大家可以看到新建的产品(道具)ID:这里Himi创建的产品ID是com.himi.wahaha ,这里要记住这个产品ID哦~

 

第二步:申请测试账号,利用沙盒测试模拟AppStore购买道具流程!

  回到itunesconnect主页中,选择“Manage Users”然后选择“Test User”,然后出现的界面如下图:


    这里Himi已经创建了两个测试账号了,点击界面中的 “Add New User”进行创建即可;记住账号和密码哈,记不住就删掉重新建 娃哈哈~(切记:不能用于真正的AppStore中使用此账号,不仅不能用,而且一旦AppStore发现后果你懂得~) 


   第三步:在项目中申请购买产品代码以及监听;

         这里关于购买的代码部分呢,我都有备注的,Himi这里就不详细讲解了,Himi只是在代码后介绍几点值得注意的地方:

 

 这里Himi是新建的一个Cocos2d的项目,然后给出HelloWorldLayer.h以及HelloWorldLayer.m的全部代码,所有购买代码也全在里面也对应有Himi的注释!

          HelloWorldLayer.h

[cpp]  view plain copy
  1. //  
  2. //  HelloWorldLayer.h  
  3. //  buytest  
  4. //  
  5. //  Created by 华明 李 on 11-10-29.  
  6. //  Copyright Himi 2011年. All rights reserved.  
  7. //  
  8.   
  9.   
  10. // When you import this file, you import all the cocos2d classes  
  11. #import "cocos2d.h"  
  12. #import   
  13.   
  14. #import   
  15. enum{  
  16.      IAP0p99=10,  
  17.      IAP1p99,  
  18.      IAP4p99,  
  19.      IAP9p99,   
  20.      IAP24p99,  
  21. }buyCoinsTag;   
  22.   
  23. @interface HelloWorldLayer : CCLayer  
  24. {  
  25.     int buyType;  
  26. }  
  27.   
  28. +(CCScene *) scene;    
  29. - (void) requestProUpgradeProductData;  
  30. -(void)RequestProductData;  
  31. -(bool)CanMakePay;                               
  32. -(void)buy:(int)type;   
  33. - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;  
  34. -(void) PurchasedTransaction: (SKPaymentTransaction *)transaction;  
  35. - (void) completeTransaction: (SKPaymentTransaction *)transaction;  
  36. - (void) failedTransaction: (SKPaymentTransaction *)transaction;  
  37. -(void) paymentQueueRestoreCompletedTransactionsFinished: (SKPaymentTransaction *)transaction;  
  38. -(void) paymentQueue:(SKPaymentQueue *) paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error;  
  39. - (void) restoreTransaction: (SKPaymentTransaction *)transaction;  
  40. -(void)provideContent:(NSString *)product;  
  41. -(void)recordTransaction:(NSString *)product;  
  42. @end  
     
              HelloWorldLayer.m

[cpp]  view plain copy
  1. //    
  2. //  IapLayer.m    
  3. //    
  4. //  Created by Himi on 11-5-25.    
  5. //  Copyright 2011年 李华明 . All rights reserved.    
  6. //    
  7.     
  8. #import "HelloWorldLayer.h"     
  9. #define ProductID_IAP0p99 @"com.buytest.one"//$0.99      
  10. #define ProductID_IAP1p99 @"com.buytest.two" //$1.99     
  11. #define ProductID_IAP4p99 @"com.buytest.three" //$4.99      
  12. #define ProductID_IAP9p99 @"com.buytest.four" //$19.99      
  13. #define ProductID_IAP24p99 @"com.buytest.five" //$24.99      
  14.     
  15. @implementation HelloWorldLayer    
  16. +(CCScene *) scene    
  17. {    
  18.     CCScene *scene = [CCScene node];    
  19.     HelloWorldLayer *layer = [HelloWorldLayer node];    
  20.     [scene addChild: layer];    
  21.     return scene;    
  22. }    
  23. -(id)init    
  24. {    
  25.     if ((self = [super init])) {    
  26.         CGSize size = [[CCDirector sharedDirector] winSize];    
  27.         CCSprite *iap_bg  = [CCSprite spriteWithFile:@"Icon.png"];      
  28.         [iap_bg setPosition:ccp(size.width/2,size.height/2)];    
  29.         [self addChild:iap_bg z:0];    
  30.         //---------------------    
  31.         //----监听购买结果    
  32.         [[SKPaymentQueue defaultQueue] addTransactionObserver:self];    
  33.         //申请购买    
  34.         /*  
  35.          enum{  
  36.          IAP0p99=10,  
  37.          IAP1p99,  
  38.          IAP4p99,  
  39.          IAP9p99,  
  40.          IAP24p99,  
  41.          }buyCoinsTag;  
  42.          */    
  43.         [self buy:IAP24p99];    
  44.     }    
  45.     return self;    
  46. }    
  47.     
  48. -(void)buy:(int)type    
  49. {     
  50.     buyType = type;      
  51.     if ([SKPaymentQueue canMakePayments]) {    
  52.         //[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];    
  53.         [self RequestProductData];      
  54.         CCLOG(@"允许程序内付费购买");    
  55.     }    
  56.     else    
  57.     {    
  58.         CCLOG(@"不允许程序内付费购买");     
  59.         UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@"Alert"     
  60.                                                             message:@"You can‘t purchase in app store(Himi说你没允许应用程序内购买)"                                                            
  61.                                                            delegate:nil cancelButtonTitle:NSLocalizedString(@"Close(关闭)",nil) otherButtonTitles:nil];    
  62.             
  63.         [alerView show];    
  64.         [alerView release];    
  65.             
  66.     }     
  67. }    
  68.      
  69. -(bool)CanMakePay    
  70. {    
  71.     return [SKPaymentQueue canMakePayments];    
  72. }    
  73.     
  74. -(void)RequestProductData    
  75. {    
  76.     CCLOG(@"---------请求对应的产品信息------------");    
  77.     NSArray *product = nil;    
  78.     switch (buyType) {    
  79.         case IAP0p99:    
  80.             product=[[NSArray alloc] initWithObjects:ProductID_IAP0p99,nil];    
  81.             break;    
  82.         case IAP1p99:    
  83.             product=[[NSArray alloc] initWithObjects:ProductID_IAP1p99,nil];    
  84.             break;    
  85.         case IAP4p99:    
  86.             product=[[NSArray alloc] initWithObjects:ProductID_IAP4p99,nil];    
  87.             break;    
  88.         case IAP9p99:    
  89.             product=[[NSArray alloc] initWithObjects:ProductID_IAP9p99,nil];    
  90.             break;    
  91.         case IAP24p99:    
  92.             product=[[NSArray alloc] initWithObjects:ProductID_IAP24p99,nil];    
  93.             break;    
  94.                 
  95.         default:    
  96.             break;    
  97.     }    
  98.     NSSet *nsset = [NSSet setWithArray:product];    
  99.     SKProductsRequest *request=[[SKProductsRequest alloc] initWithProductIdentifiers: nsset];    
  100.     request.delegate=self;    
  101.     [request start];    
  102.     [product release];    
  103. }    
  104. // 请求协议    
  105. //收到的产品信息    
  106. - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{    
  107.         
  108.     NSLog(@"-----------收到产品反馈信息--------------");    
  109.     NSArray *myProduct = response.products;    
  110.     NSLog(@"产品Product ID:%@",response.invalidProductIdentifiers);    
  111.     NSLog(@"产品付费数量: %d", [myProduct count]);    
  112.     // populate UI     
  113.     for(SKProduct *product in myProduct){    
  114.         NSLog(@"product info");    
  115.         NSLog(@"SKProduct 描述信息%@", [product description]);       
  116.         NSLog(@"产品标题 %@" , product.localizedTitle);    
  117.         NSLog(@"产品描述信息: %@" , product.localizedDescription);    
  118.         NSLog(@"价格: %@" , product.price);    
  119.         NSLog(@"Product id: %@" , product.productIdentifier);     
  120.     }     
  121.     SKPayment *payment = nil;     
  122.     switch (buyType) {    
  123.         case IAP0p99:    
  124.             payment  = [SKPayment paymentWithProductIdentifier:ProductID_IAP0p99];    //支付$0.99    
  125.             break;    
  126.         case IAP1p99:    
  127.             payment  = [SKPayment paymentWithProductIdentifier:ProductID_IAP1p99];    //支付$1.99    
  128.             break;    
  129.         case IAP4p99:    
  130.             payment  = [SKPayment paymentWithProductIdentifier:ProductID_IAP4p99];    //支付$9.99    
  131.             break;    
  132.         case IAP9p99:    
  133.             payment  = [SKPayment paymentWithProductIdentifier:ProductID_IAP9p99];    //支付$19.99    
  134.             break;    
  135.         case IAP24p99:    
  136.             payment  = [SKPayment paymentWithProductIdentifier:ProductID_IAP24p99];    //支付$29.99    
  137.             break;    
  138.         default:    
  139.             break;    
  140.     }    
  141.     CCLOG(@"---------发送购买请求------------");    
  142.     [[SKPaymentQueue defaultQueue] addPayment:payment];      
  143.     [request autorelease];     
  144.         
  145. }    
  146. - (void)requestProUpgradeProductData    
  147. {    
  148.     CCLOG(@"------请求升级数据---------");    
  149.     NSSet *productIdentifiers = [NSSet setWithObject:@"com.productid"];    
  150.     SKProductsRequest* productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];    
  151.     productsRequest.delegate = self;    
  152.     [productsRequest start];     
  153.         
  154. }    
  155. //弹出错误信息    
  156. - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{    
  157.     CCLOG(@"-------弹出错误信息----------");    
  158.     UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Alert",NULL) message:[error localizedDescription]    
  159.                                                        delegate:nil cancelButtonTitle:NSLocalizedString(@"Close",nil) otherButtonTitles:nil];    
  160.     [alerView show];    
  161.     [alerView release];    
  162. }    
  163.     
  164. -(void) requestDidFinish:(SKRequest *)request     
  165. {    
  166.     NSLog(@"----------反馈信息结束--------------");    
  167.         
  168. }    
  169.      
  170. -(void) PurchasedTransaction: (SKPaymentTransaction *)transaction{    
  171.     CCLOG(@"-----PurchasedTransaction----");    
  172.     NSArray *transactions =[[NSArray alloc] initWithObjects:transaction, nil];    
  173.     [self paymentQueue:[SKPaymentQueue defaultQueue] updatedTransactions:transactions];    
  174.     [transactions release];    
  175. }     
  176.     
  177. // 千万不要忘记绑定,代码如下:    
  178. //----监听购买结果    
  179. //[[SKPaymentQueue defaultQueue] addTransactionObserver:self];    
  180.     
  181. - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions//交易结果    
  182. {    
  183.     CCLOG(@"-----paymentQueue--------");    
  184.     for (SKPaymentTransaction *transaction in transactions)    
  185.     {    
  186.         switch (transaction.transactionState)    
  187.         {     
  188.             case SKPaymentTransactionStatePurchased://交易完成     
  189.                 [self completeTransaction:transaction];    
  190.                 CCLOG(@"-----交易完成 --------");    
  191.                 CCLOG(@"不允许程序内付费购买");     
  192.                 UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@"Alert"     
  193.                                                                     message:@"Himi说你购买成功啦~娃哈哈"                                                          
  194.                                                                    delegate:nil cancelButtonTitle:NSLocalizedString(@"Close(关闭)",nil) otherButtonTitles:nil];    
  195.                     
  196.                 [alerView show];    
  197.                 [alerView release];     
  198.                 break;     
  199.             case SKPaymentTransactionStateFailed://交易失败     
  200.                 [self failedTransaction:transaction];    
  201.                  CCLOG(@"-----交易失败 --------");    
  202.                 UIAlertView *alerView2 =  [[UIAlertView alloc] initWithTitle:@"Alert"     
  203.                                                                     message:@"Himi说你购买失败,请重新尝试购买~"                                                          
  204.                                                                    delegate:nil cancelButtonTitle:NSLocalizedString(@"Close(关闭)",nil) otherButtonTitles:nil];    
  205.                     
  206.                 [alerView2 show];    
  207.                 [alerView2 release];    
  208.                 break;     
  209.             case SKPaymentTransactionStateRestored://已经购买过该商品     
  210.                 [self restoreTransaction:transaction];    
  211.                  CCLOG(@"-----已经购买过该商品 --------");    
  212.             case SKPaymentTransactionStatePurchasing:      //商品添加进列表    
  213.                  CCLOG(@"-----商品添加进列表 --------");    
  214.                 break;    
  215.             default:    
  216.                 break;    
  217.         }    
  218.     }    
  219. }    
  220. - (void) completeTransaction: (SKPaymentTransaction *)transaction    
  221.     
  222. {    
  223.     CCLOG(@"-----completeTransaction--------");    
  224.     // Your application should implement these two methods.    
  225.     NSString *product = transaction.payment.productIdentifier;    
  226.     if ([product length] > 0) {    
  227.             
  228.         NSArray *tt = [product componentsSeparatedByString:@"."];    
  229.         NSString *bookid = [tt lastObject];    
  230.         if ([bookid length] > 0) {    
  231.             [self recordTransaction:bookid];    
  232.             [self provideContent:bookid];    
  233.         }    
  234.     }    
  235.         
  236.     // Remove the transaction from the payment queue.    
  237.         
  238.     [[SKPaymentQueue defaultQueue] finishTransaction: transaction];    
  239.         
  240. }    
  241.     
  242. //记录交易    
  243. -(void)recordTransaction:(NSString *)product{    
  244.     CCLOG(@"-----记录交易--------");    
  245. }    
  246.     
  247. //处理下载内容    
  248. -(void)provideContent:(NSString *)product{    
  249.     CCLOG(@"-----下载--------");     
  250. }    
  251.     
  252. - (void) failedTransaction: (SKPaymentTransaction *)transaction{    
  253.     NSLog(@"失败");    
  254.     if (transaction.error.code != SKErrorPaymentCancelled)    
  255.     {    
  256.     }    
  257.     [[SKPaymentQueue defaultQueue] finishTransaction: transaction];    
  258.         
  259.         
  260. }    
  261. -(void) paymentQueueRestoreCompletedTransactionsFinished: (SKPaymentTransaction *)transaction{    
  262.         
  263. }    
  264.     
  265. - (void) restoreTransaction: (SKPaymentTransaction *)transaction    
  266.     
  267. {    
  268.     NSLog(@" 交易恢复处理");    
  269.         
  270. }    
  271.     
  272. -(void) paymentQueue:(SKPaymentQueue *) paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error{    
  273.     CCLOG(@"-------paymentQueue----");    
  274. }    
  275.     
  276.     
  277. #pragma mark connection delegate    
  278. - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data    
  279. {    
  280.     NSLog(@"%@",  [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);     
  281. }    
  282. - (void)connectionDidFinishLoading:(NSURLConnection *)connection{    
  283.         
  284. }    
  285.     
  286. - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{    
  287.     switch([(NSHTTPURLResponse *)response statusCode]) {    
  288.         case 200:    
  289.         case 206:    
  290.             break;    
  291.         case 304:     
  292.             break;    
  293.         case 400:     
  294.             break;      
  295.         case 404:    
  296.             break;    
  297.         case 416:    
  298.             break;    
  299.         case 403:    
  300.             break;    
  301.         case 401:    
  302.         case 500:    
  303.             break;    
  304.         default:    
  305.             break;    
  306.     }            
  307. }    
  308.     
  309. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {    
  310.     NSLog(@"test");    
  311. }    
  312.     
  313. -(void)dealloc    
  314. {    
  315.     [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];//解除监听  
  316.     [super dealloc];    
  317. }     
  318. @end  



    代码注释的相当清楚了,没有什么可解释的,这里说几点值得注意的地方:

1.添加对应对应代码时不要忘记,添加框架 StoreKit.framework,如何添加框架请看我的博文【iOS-Cocos2d游戏开发之十四】音频/音效/视频播放(利用Cocos2D-iPhone-Extensions嵌入Cocos2d进行视频播放!)!

2. 越狱机器无法沙盒测试!模拟器的话,Himi用4.3模拟器不可以,因为提示没有开启程序内付费- -(我都没看到模拟器有store的选项,so~);但是使用iOS5的模拟器可以测试沙盒,但是执行的顺序会有些问题,但是还没真机的童鞋可以使用,建议一切以真机实测为准

3. 千万不要忘记在iTunesConnect中创建App Bundle ID一定要跟你的项目中的info.plist中的Bundle ID保证一致!!!!

4. 以上代码中你需要修改的就是我在HelloWorldLayer.m类中的宏定义的Product ID(产品ID),例如Himi刚才新建了一个产品ID是“com.himi.wahaha"


然后我运行项目截图如下以及运行控制台打印的信息如下:


点击Buy之后运行截图以及打印信息:


输入测试账号密码后以及打印信息:



                这里Himi最后一张截图是没有购买成功,这里Himi是故意截图出来的,原因就是想告诉童鞋们:

 如果你的产品信息能够正常得到,但是始终无法成功的话,不要着急,因为你的产品要进入iTunes Connect,并且Apple准备好沙箱环境需要一些时间。Himi之前遇到过,然后在过了段时间后我没有修改任何一行代码,但产品ID变为有效并能成功购买。=。 =郁闷ing~~ 其实要使产品发布到Apple的网络系统是需要一段时间的,so~这里别太着急!

           越狱机器无法正常测试沙盒的喔~

顺便提示一下:Bundle ID 尽可能与开发者证书的app ID 一致。


(二)

下一步:

本章中所示代码可用于内置型商品模式(Built-in)。 如果你的程序要使用服务器来发布商品,你需要负责设计和执行iPhone程序和你的服务器之间的通信。服务器应该验证数据并为程序提供内容。

验证store的收据

使用服务器来交付内容,我们还需要做些额外的工作来验证从Store Kit发送的收据信息。

重要信息:来自Store的收据信息的格式是专用的。 你的程序不应直接解析这类数据。可使用如下的机制来取出其中的信息。

验证App Store返回的收据信息

当交易完成时,Store Kit告知payment observer这个消息,并返回完成的transaction。 SKPaymentTransaction的transactionReceipt属性就包含了一个经过签名的收据信息,其中记录了交易的关键信息。你的 服务器要负责提交收据信息来确定其有效性,并保证它未经过篡改。 这个过程中,信息被以JSON数据格式发送给App Store,App Store也以JSON的格式返回数据。(大家可以先了解一下JSON的格式)

验证收据的过程:

1. 从transaction的transactionReceipt属性中得到收据的数据,并以base64方式编码。

2. 创建JSON对象,字典格式,单键值对,键名为"receipt-data", 值为上一步编码后的数据。效果为:

   
   
   
   
  1. {   
  2.     "receipt-data"    : "(编码后的数据)"  
  3. }  

3. 发送HTTP POST的请求,将数据发送到App Store,其地址为:
https://buy.itunes.apple.com/verfyReceipt

4. App Store的返回值也是一个JSON格式的对象,包含两个键值对, status和receipt:

   
   
   
   
  1. {   
  2.     "status"    : 0,   
  3.     "receipt"    : { … }   
  4. }  

如果status的值为0, 就说明该receipt为有效的。 否则就是无效的。

App Store的数据

发送给App Store的收据数据是通过对transaction中对应的信息编码而创建的。 当App Store验证收据时, 将从其中解码出数据,并以"receipt"的键返回。 返回的响应信息是JSON格式,被包含在SKPaymentTransaction的对象中(transactionReceipt属性)。Server 可通过这些值来了解交易的详细信息。 Apple建议只发送receipt数据到服务器并使用receipt数据验证和获得交易详情。 因为App Store可验证收据信息,返回信息,保证信息不被篡改,这种方式比同时提交receipt和transaction的数据要安全。(这段得再看看)

表5-1为交易信息的所有键,很多的键都对应SKPaymentTransaction的属性。

备注:一些键取决于你的程序是链接到App Store还是测试用的Sandbox环境。更多关于sandbox的信息,请查看"Testing a Store"一章。

Table 5-1 购买信息的键:

测试Store功能

开发过程中,我们需要测试支付功能以保证其工作正常。然而,我们不希望在测试时对用户收费。 Apple提供了sandbox的环境供我们测试。

备注:Store Kit在模拟器上无法运行。 当在模拟器上运行Store Kit的时候,访问payment queue的动作会打出一条警告的log。测试store功能必须在真机上进行。

Sandbox环境

使用Sandbox环境的话,Store Kit并没有链接到真实的App Store,而是链接到专门的Sandbox环境。 SandBox的内容和App Store一致,只是它不执行真实的支付动作。 它会返回交易成功的信息。 Sandbox使用专门的iTunes Connect测试 账户。不能使用正式的iTunes Connect账户来测试。

要测试程序,需要创建一个专门的测试账户。你至少需要为程序的每个区域创建至少一个测试账户。详细信息,请查看iTunes Connect Developer Guide文档。

在Sandbox环境中测试

步骤:

1. 在测试的iPhone上退出iTunes账户

Settings中可能会记录之前登录的账户,进入并退出。

重要信息:不能在Settings 程序中通过测试账户登录。

2. 运行程序

当你在程序的store中购买商品后,Store kit提示你去验证交易。用测试账户登录,并批准支付。 这样虚拟的交易就完成了。

在Sandbox中验证收据

验证的URL不同了:

   
   
   
   
  1. NSURL *sandboxStoreURL = [[NSURL alloc]initWithString:    
  2. @"https://sandbox.itunes.apple.com/verifyReceipt"];

苹果审核app时,仍然在沙盒环境下测试。但是客户端同事在app提交苹果审核时,将issandbox字段写死,设置为生产环境。这样就导致沙盒收据发送到https://buy.itunes.apple.com/verifyReceipt去验证。

那么如何自动的识别收据是否是sandbox receipt呢?

识别沙盒环境下收据的方法有两种:

1.根据收据字段 environment = sandbox。

2.根据收据验证接口返回的状态码

如果status=21007,则表示当前的收据为沙盒环境下收据, t进行验证。

苹果反馈的状态码;

21000App Store无法读取你提供的JSON数据
21002 收据数据不符合格式
21003 收据无法被验证
21004 你提供的共享密钥和账户的共享密钥不一致
21005 收据服务器当前不可用
21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
21008 收据信息是产品环境中使用,但却被发送到测试环境中验证

先生产验证后测试验证,可以避免来回切换接口的麻烦。测试验证只要用你自己申请的测试appid的时候才会用到,用户不会拥有测试appid,所以不会走到测试验证这一步。即使生产验证出错,应该也不回返回21007状态吗。测试验证通过的用户名,和充值金额最好用数据库记录下来,方便公司资金核对。




你可能感兴趣的:(iOS开发)