天天看點

iOS IAP 入門 注冊流程 和問題。

本站文章均為 李華明Himi 原創,轉載務必在明顯處注明:

轉載自【黑米GameDev街區】 原文連結: http://www.himigame.com/iphone-cocos2d/550.html

          ☞ 點選訂閱 ☜ 本部落格最新動态!及時将最新博文通知您!

在應用内嵌入付費代碼這一快Himi可以直接将代碼分享給大家,是以我們來說一些主要流程,畢竟沒有接觸過這一塊的童鞋肯定相當頭疼 =。  =

OK,步入整體,如果你想在iOS裡内嵌收費,那麼分為以下幾步:

 【提示:以下建立App部分内容,你不用非要等項目能打包了才開始做,可以随時并且随便的建立個測試項目即可,因為嵌入付費并不要求上傳App的ipa包的!!】   

第一步:你需要在iTunesConnect中建立個新的App,然後為這個App設定一些産品(付費道具)等;

OK,這裡Himi稍微解釋下,iTunesConnect是蘋果提供的一個平台,主要提供AP釋出和管理App的,最重要的功能是建立管理項目資訊,項目付費産品(道具)管理、付費的測試賬号、送出App等等,這裡就簡單介紹這麼多,關于産品一詞在此我們可以了解成遊戲道具即可;在蘋果看來所有付費都屬于産品 =。 =千萬不要糾結字眼哦~

OK,打開iTunesConnect網站:https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa (注意:企業級的使用者必須使用公司主開發者賬号登陸才可!)

成功登陸後的頁面如下:

iOS IAP 入門 注冊流程 和問題。

這裡大概說下重要的一些項:

   Contracts, Tax, and Banking   : 管理銀行賬号、聯系人以及稅等等;這裡要根據提示完成對應的資訊填寫!一定要詳細填寫喔~

             Manage Users :管理使用者的,比如主賬号以及測試付費的(測試App)賬号;

             Manage Your Applictions:管理應用程式的,你所有釋出的應用和每個應用的狀态都在這裡面;

下面我們建立一個App項目,大家放心,我們這裡建立的是不會直接送出給App稽核的,是以放心建立,隻要控制好App的狀态不要是待稽核狀态即可,不過即使你不小心将項目送出了,也沒事,直接更改App狀态即可了;

選擇Manage Your Applictions選項,然後建立一個項目:【Add New App】,根據提示來填寫吧,這裡就不細緻說明了~

建立好一個App之後,在點選Manage Your Applictions後的界面應該如下:

iOS IAP 入門 注冊流程 和問題。

這裡你将看到自己建立的App,點選你建立的App項目,這裡Himi建立的項目名字叫”ProjectForBuyTest“,點選你的App進入如下界面:

iOS IAP 入門 注冊流程 和問題。

(注意:這裡的Bundle ID一定要跟你的項目中的info.plist中的Bundle ID保證一緻!!!!)

這裡可以管理你的項目的資訊、狀态、是否嵌入GameCenter等等選項,那麼本章我們重點介紹如何使用IAp沙盒測試程式内付費,是以這裡我們點選右上角的”Manage In-App Purchases“選項進入建立産品(遊戲道具)界面如下:

iOS IAP 入門 注冊流程 和問題。

上圖中的下方看到Himi建立過的四個産品(道具)了,你可以點選”Create New“選項建立一個産品(付費道具),點選建立如下界面:

iOS IAP 入門 注冊流程 和問題。

上圖中Himi沒有截圖出所有的選項,這裡大概介紹下,這個界面是選擇你的消費道具的種類,種類說明如下:

   類型選擇有四種選擇:

   1.Consumable(消耗品): 每次下載下傳都需要付費;

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

   3.Auto-Renewable Subscriptions:自動訂閱;

   4.Free Subscription:免費訂閱

   最下方是你沙盒測試的截圖,暫且不管即可;

   這裡Himi選擇Consumable選項,比如很多遊戲都是購買金币啦這樣子就可以選擇這個;然後出現如下界面:

iOS IAP 入門 注冊流程 和問題。

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“選項然後出現如下界面:

iOS IAP 入門 注冊流程 和問題。

上圖中的選項:

Language:語言

      Displayed Name(顯示名稱): 使用者看到的産品名稱。

      Description(描述): 對産品進行描述。

Ok,一路 Save儲存回到”Manage In-App Purchases“界面中會看到我們建立的産品(道具)如下:

iOS IAP 入門 注冊流程 和問題。

大家可以看到建立的産品(道具)ID:這裡Himi建立的産品ID是com.himi.wahaha ,這裡要記住這個産品ID哦~

第二步:申請測試賬号,利用沙盒測試模拟AppStore購買道具流程!

  回到itunesconnect首頁中,選擇“Manage Users”然後選擇“Test User”,然後出現的界面如下圖:

iOS IAP 入門 注冊流程 和問題。

這裡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];

//申請購買

[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”

然後我運作項目截圖如下以及運作控制台列印的資訊如下:

iOS IAP 入門 注冊流程 和問題。
iOS IAP 入門 注冊流程 和問題。
iOS IAP 入門 注冊流程 和問題。
iOS IAP 入門 注冊流程 和問題。

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

iOS IAP 入門 注冊流程 和問題。

 如果你的産品資訊能夠正常得到,但是始終無法成功的話,不要着急,因為你的産品要進入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

上面是轉載,下面加上自己的一些感觸:

需要注意的點:

1.每個APP都是單獨的具有IAP的,如果自己要建立一個APP測試支付的話,就需要在Itunes Connect中新增測試APP的資訊,并在測試APP中添加測試使用的付費産品。

2.如果你程式的第一個版本沒有IAP功能,在第二個版本中想加入。可能會遇到 ”無法連接配接到 iTunes Store“的問題。 這個時候,你需要把自己的程式從測試機器上删除,然後在Xcode中 clean 然後編譯運作。 全部清理一遍再運作,貌似就可以了。不知道問什麼。