詳解iOS應用程序內使用IAP/StoreKit付費、沙盒(SandBox)測試、創建測試賬號流程

【iOS開發必收藏】詳解iOS應用程序內使用IAP/StoreKit付費、沙盒(SandBox)測試、創建測試賬號流程!【2012-12-11日更新獲取”產品付費數量等於0的問題”】

(作者新浪微博: @李華明Himi 
轉載自【黑米GameDev街區】 原文鏈接: http://www.himigame.com/iphone-cocos2d/550.html
 

//——2012-12-11日更新   獲取”產品付費數量等於0這個問題”的原因

看到很多童鞋問到,爲什麼每次都返回數量等於0??

其實有童鞋已經找到原因了,原因是你在 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

//——-

在應用內嵌入付費代碼這一快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

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

HelloWorldLayer.m

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

然後我運行項目截圖如下以及運行控制檯打印的信息如下:

       害羞這裏Himi最後一張截圖是沒有購買成功,這裏Himi是故意截圖出來的,原因就是想告訴童鞋們: :tx:

 如果你的產品信息能夠正常得到,但是始終無法成功的話,不要着急,因爲你的產品要進入iTunes Connect,並且Apple準備好沙箱環境需要一些時間。Himi之前遇到過,然後在過了段時間後我沒有修改任何一行代碼,但產品ID變爲有效並能成功購買。=。 =鬱悶ing~~ 其實要使產品發佈到Apple的網絡系統是需要一段時間的,so~這裏別太着急!

           越獄機器無法正常測試沙盒的喔~

順便提示一下:Bundle ID 儘可能與開發者證書的app ID 一致。

    好了,寫了這麼多了,咳咳、Himi繼續忙了,做iOS的童鞋們我想此篇將成爲你必須收藏的一篇哦~嘿嘿!

 

2012-3-13日更新內容:

1.驗證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”, 值爲上一步編碼後的數據。效果爲:
{
“receipt-data” : “(編碼後的數據)”
}

3. 發送HTTP POST的請求,將數據發送到App Store,其地址爲:
https://buy.itunes.apple.com/verifyReceipt

4. App Store的返回值也是一個JSON格式的對象,包含兩個鍵值對, status和receipt:
{
“status” : 0,
“receipt” : { … }
}

如果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 購買信息的鍵:

鍵名 描述
quantity 購買商品的數量。對應SKPayment對象中的quantity屬性
product_id 商品的標識,對應SKPayment對象的productIdentifier屬性。
transaction_id 交易的標識,對應SKPaymentTransaction的transactionIdentifier屬性
purchase_date 交易的日期,對應SKPaymentTransaction的transactionDate屬性
original_-transaction_id 對於恢復的transaction對象,該鍵對應了原始的transaction標識
original_purchase_-date 對於恢復的transaction對象,該鍵對應了原始的交易日期
app_item_id App Store用來標識程序的字符串。一個服務器可能需要支持多個server的支付功能,可以用這個標識來區分程序。鏈接sandbox用來測試的程序的不到這個值,因此該鍵不存在。
version_external_-identifier 用來標識程序修訂數。該鍵在sandbox環境下不存在
bid iPhone程序的bundle標識
bvrs iPhone程序的版本號

測試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不同了:
NSURL *sandboxStoreURL = [[NSURL alloc]initWithString:
@”https://sandbox.itunes.apple.com/verifyReceipt“];

2. 自動更新的訂閱服務

In-App Purchase提供了自動更新型訂閱服務的標準方式。自動更新型訂閱有如下新的顯著特徵:

1. 當你在iTunes Connect中配置自動更新型訂閱服務時,需要同時指定更新週期和其他的促銷選項。
2. 自動更新型訂閱服務會被自動恢復(使用Store Kit中恢復非消費型商品一樣的函數)。原始的交易信息會和更新的交易信息一起發送給你的程序。詳情請查看“Restoring Transactions”一節。
3. 當你的服務器向App Store驗證收據(receipt),訂閱服務被激活並更新時,App store會向你的app返回更新後的收據信息。

3.爲你的商店添加自動更新型訂閱服務

按以下步驟來實現自動更新型訂閱服務:
1. 連接iTunes Connect網站,並創建一個共享密鑰。共享密鑰是一個密碼,你的服務器在驗證自動更新型訂閱服務的時候必須提供這個密碼。共享密鑰爲App Store的交易增加了一層保護。(詳情,請參考iTunes Connect Developer Guide文檔)

2. 在iTunes Connect中創建並配置新的自動更新型訂閱服務商品。

3. 修改服務器端關於驗證收據部分的代碼,添加共享密鑰到驗證信息用的JSON數據中。服務器的驗證代碼需要可以解析App store的返回數據以判斷訂閱是否過期。如果訂閱服務已經被用戶更新,最新的收據也會返回給你的server。

設計iOS客戶端

大多數情況下,iOS客戶端程序應做出最小新改來支持自動更新型訂閱服務。事實上,客戶端程序需要做的更簡單,你可以使用非消費型(non-consumable)商品的流程來做自動更新型訂閱服務的事情。你的程序在不同時期會收到單獨的交易信息來告知訂閱已被更新。程序應該單獨驗證每一條收據。

驗證自動更新型訂閱服務的收據

驗證自動更形型訂閱服務的收據和之前講到的“驗證收據”的方式一致。你的程序創建一個JSON對象並把它發送給App Store。自動更新型訂閱服務的JSON對象必須包含另外的參數——就是你在iTunes Connect中創建的共享密鑰。

{
“receipt-data” : “(actual receipt bytes here)”
“password” : “(shared secret bytes here)”
}

返回內容包含了狀態信息,用來標識收據是否驗證有效。

{
“status” : 0,
“receipt” : { … }
“latest_receipt” : “(base-64 encoded receipt)”
“latest_receipt_info” : { … }
}

如果用戶的收據是有效的,訂閱被激活,則status的值爲0。receipt對應的值爲解碼後的收據信息。如果你的服務器收到了非零值的狀態碼,對照表7-1查看:

表7-1 自動更新型訂閱服務返回狀態碼
狀態碼 描述
21000 App Store無法讀取你提供的JSON數據
21002 收據數據不符合格式
21003 收據無法被驗證
21004 你提供的共享密鑰和賬戶的共享密鑰不一致
21005 收據服務器當前不可用
21006 收據是有效的,但訂閱服務已經過期。當收到這個信息時,解碼後的收據信息也包含在返回內容中
21007 收據信息是測試用(sandbox),但卻被髮送到產品環境中驗證
21008 收據信息是產品環境中使用,但卻被髮送到測試環境中驗證
注意:在這裏的非零狀態碼只是針對自動更新型訂閱服務,不能將這些狀態碼用在測試其他類型產品的返回值中。

JSON數據中的receipt欄位包含了解析過的收據信息。自動更新型訂閱服務包含了一些新加的信息。請參考表7-2:

表7-2 自動更新型訂閱服務的信息:

鍵名 描述
expires_date 訂閱的過期時間,顯示方式是從Jan 1, 1970, 00:00:00 GMT計算到過期時間的毫秒數。這個鍵不包含在恢復的交易信息中。
original_transaction_id 初次購買的交易標識。所有訂閱的更新和恢復交易都共享這個標識
original_purchase_date 初次購買(訂閱)的日期。
purchase_date 交易的日期。對於更新訂閱的交易來說,這個日期表示更新日期。如果從App Store解析的數據是最新的訂閱收據,這個值表示最近更新訂閱的日期。

除了receipt-Data信息外,返回內容還可能包含另外兩個信息。如果用戶的訂閱服務被激活並更新。則latest_receipt信息會被以base-64方式編碼幷包含在返回內容中。解碼後的新的收據信息也會在latest_expired_receipt_info包含。你的服務器可以使用新的收據來維護最新更新訂閱的信息。

4. 如果交易是恢復過來的(restore),我們用這個方法來處理:

- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{
[self recordTransaction: transaction];
[self provideContent: transaction.payment.productIdentifier];

[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
這個過程完成購買的過程類似。 恢復的購買內容提供一個新的交易信息,這個信息包含了新的transaction的標識和receipt數據。 如果需要的話,你可以把這些信息單獨保存下來,供追溯審(我們的)查之用。但更多的情況下,在交易完成時,你可能需要覆蓋原始的transaction數據,並使用其中的商品標識。

更新內容參考文章:http://www.cocoachina.com/bbs/read.php?tid-24738.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章