iPhone In App Purchase購買完成時驗證transactionReceipt
最近正在做一個iphone遊戲內購買的專案,所以瞭解了一些In App Purchase相關的技術。
根據Apple官方文件,In App Purchase(IAP)有兩種模型:內建模型(Built-in Model)和伺服器模型(Server Model)。由於我做的專案需要用自己的伺服器管理虛擬貨幣,因此自然就選擇了伺服器模型。
由於IAP的整個流程比較複雜,一篇部落格的篇幅無法完全介紹清楚,所以我將用幾篇部落格的篇幅來做介紹。在這篇部落格裡就僅僅介紹IAP購買完成後如何通過將客戶端收到的transactionReceipt傳送到我自己的伺服器(我用的Google App Engine,Python環境),再由我的伺服器
以下是驗證receipt的詳細流程:
IAP在購買流程中,會給每一次購買行為建立一個SKPaymentTransaction,這個transaction會記錄使用者購買行為的狀態,如正在購買(SKPaymentTransactionStatePurchasing),已購買(SKPaymentTransactionStatePurchased),購買失敗(SKPaymentTransactionStateFailed)等。而當transaction狀態是 SKPaymentTransactionStatePurchased的時候,客戶端就能得到一個transaction.
第一步:傳送receipt
首先在SKPaymentTransaction的狀態改變的時候,會呼叫一個delegate:- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;。我可以通過transactions陣列得到裡面的每一個transaction。再判斷它的transactionState,如果是等於SKPaymentTransactionStatePurchased,就呼叫另外一個函式:completeTransaction,這個函式的具體實現:
這個函式首先是執行了一段encode操作。這段操作是對transactionReceipt做了一次base64的編碼(根據apple文件的要求)。編碼的程式碼是我從網上找到的,程式碼如下:
- (NSString * )encode:( const uint8_t * )input length:(NSInteger)length { static char table[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= " ; NSMutableData * data = [NSMutableData dataWithLength:((length + 2 ) / 3 ) * 4 ]; uint8_t * output = (uint8_t * )data.mutableBytes; for (NSInteger i = 0 ; i < length; i += 3 ) { NSInteger value = 0 ; for (NSInteger j = i; j < (i + 3 ); j ++ ) { value <<= 8 ; if (j < length) { value |= ( 0xFF & input[j]); } } NSInteger index = (i / 3 ) * 4 ; output[index + 0 ] = table[(value >> 18 ) & 0x3F ]; output[index + 1 ] = table[(value >> 12 ) & 0x3F ]; output[index + 2 ] = (i + 1 ) < length ? table[(value >> 6 ) & 0x3F ] : ' = ' ; output[index + 3 ] = (i + 2 ) < length ? table[(value >> 0 ) & 0x3F ] : ' = ' ; } return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];} 原文連結: http://stackoverflow.com/questions/1298998/verify-receipt-for-in-app-purchaseencode完成後,就開始將編碼過的receipt傳送到我的伺服器。這一段很簡單,只要先建立一段url格式的字串,再建立url,然後在初始化request的時候新增這個url,最後建立connection就行了。其中VMRequest是我寫的一個繼承自NSMutableURLRequest的類,這裡可以直接用NSMutableURLRequest代替。需要注意的是這裡的request最好使用POST方式,因為GET方式很可能因為傳送的receipt資料量過大而導致資料丟失。VMRequestQueue是VMRequest的佇列,這個佇列會自動按順序執行佇列中的request,這部分可以改成直接建立NSURLConnection。總之,只要建立好需要訪問的URL,再用POST方式傳送到自己的伺服器,這第一步就完成了。
第二步:向app store驗證
這一步中所做的事全部是在伺服器端完成。我的伺服器使用的是Google App Engine(GAE)的Python環境。
首先,伺服器收到receipt。因為這段receipt是已經經過編碼的,所以不需要再度編碼,唯一要做的就是生成一個json格式的資料,傳送到app store並獲取返回的資料。具體程式碼如下:
import httplib # have to import simplejson because google app engine uses python 2.5. When it upgrades, can import json directly import simplejson as jsonjsonStr = json.dumps({ " receipt-data " : receipt}) # connect = httplib.HTTPSConnection("buy.itunes.apple.com") # sandbox connect = httplib.HTTPSConnection( " sandbox.itunes.apple.com " )headers = { " Content-type " : " application/json " }connect.request( " POST " , " /verifyReceipt " , jsonStr)result = connect.getresponse()data = result.read()connect.close()decodedJson = json.loads(data)status = decodedJson[u ' status ' ] if status == 0: return decodedJson else : return Falsesimplejson是python2.5支援的一個json庫,使用simplejson的原因是GAE只支援到python2.5。如果GAE版本更新到2.6及以上了,那就可以直接import json。
json物件的格式有點類似於一個dictionary,一個key對應於一個value。
json.dumps是將一個json物件序列化成一段字串,相對的json.loads就是將一段字串反序列化成一個json物件。
httplib是一個python自帶的庫,用來做http連線。根據apple文件,程式碼中的request型別必須是POST,這一點需要注意。
最後,在讀取json物件的資料的時候,必須要在key的前面加“u”。
結果:伺服器收到解碼的receipt
伺服器收到的解碼的receipt格式是:
{u'status': < status>, u'receipt': {u'product_id': u'<product_id>', u'original_transaction_id': u'< original_transaction_id>', u'bid': u'<bid>', u'original_purchase_date': u'< original_purchase_date>', u'bvrs': u'< bvrs>', u'purchase_date': u'< purchase_date>', u'item_id': u'<item_id>', u'transaction_id': u'< transaction_id>', u'quantity': u'< quantity>'}}
這其中,status就表示receipt是否通過驗證;receipt後又是一個json物件,裡面product_id就是使用者購買的IAP產品的id。
最後一步,判斷status是否等於0。如果等於0,就執行使用者購買後應該實現的操作,最終再將結果反映到客戶端上。