=================1.先說下驗證方式==============
IOS 內購支付兩種模式:
內置模式
服務器模式
內置模式的流程:
1.app從app store 獲取產品信息
2.用戶選擇需要購買的產品
3.app發送支付請求到app store
4.app store 處理支付請求,并返回transaction信息
5.app將購買的內容展示給用戶
內置模式可以這樣進行本地驗單
//本地驗證
- (void)localPaymentTransactionVerify:(NSString *)environment body:(NSData *)postData transaction:(SKPaymentTransaction *)transaction{
NSURL *StoreURL = nil;
if ([environment isEqualToString:@"environment=Sandbox"]) {
StoreURL = [[NSURL alloc] initWithString: ITMS_SANDBOX_VERIFY_RECEIPT_URL];
}else {
StoreURL = [[NSURL alloc] initWithString: ITMS_PRODUCT_VERIFY_RECEIPT_URL];
}
NSLog(@"運行環境是--- %@", StoreURL);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:StoreURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:postData];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSString *product = transaction.payment.productIdentifier;
NSLog(@"transaction.payment.productIdentifier++++-----%@",product);
if ([product length] > 0)
{
NSArray *tt = [product componentsSeparatedByString:@"."];
NSString *bookid = [tt lastObject];
if([bookid length] > 0)
{
NSLog(@"打印bookid------%@",bookid);
}
}
//在此做交易記錄
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
#pragma mark connection delegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//進行二次驗證;
// NSLog(@" 以下是HTTP協議的監聽,若由服務器驗證,可不用這段代碼%@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
NSLog(@" 以下是HTTP協議的監聽,若由服務器驗證,可不用這段代碼%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
NSDictionary * dic=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
NSNumber *status=dic[@"status"];
NSDictionary * receiptDic=dic[@"receipt"];
// NSString *purchased=receiptDic[@"product_id"];
NSArray * tempArr=receiptDic[@"in_app"];
NSString * purchased=nil;
for (int i=0 ; i<tempArr.count; i++) {
NSDictionary * tempPurchase=tempArr[i];
purchased=tempPurchase[@"product_id"];
}
if (status.intValue==0) {
// 發送通知更改賬戶V豆的數量;
// [[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchasedNotification object:purchased];
}
else
{
NSLog(@"收據校驗失敗");
switch (status.intValue) {
case 21000:
NSLog(@"App Store不能讀取你提供的JSON對象");
break;
case 21002:
NSLog(@"receipt-data域的數據有問題");
break;
case 21003:
NSLog(@"receipt無法通過驗證");
break;
case 21004:
NSLog(@"提供的shared secret不匹配你賬號中的shared secret");
break;
case 21005:
NSLog(@"receipt服務器當前不可用");
break;
case 21006:
NSLog(@"receipt合法,但是訂閱已過期。服務器接收到這個狀態碼時,receipt數據仍然會解碼并一起發送");
break;
case 21007:
NSLog(@"receipt是Sandbox receipt,但卻發送至生產系統的驗證服務");
break;
case 21008:
NSLog(@"receipt是生產receipt,但卻發送至Sandbox環境的驗證服務");
break;
default:
break;
}
}
NSLog(@"%@",dic[@"status"]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
// NSLog(@" 以下是HTTP協議的監聽,若由服務器驗證,可不用這段代碼");
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(@" 以下是HTTP協議的監聽,若由服務器驗證,可不用這段代碼");
NSLog(@"%@+++",response);
switch([(NSHTTPURLResponse *)response statusCode]) {
case 200:
NSLog(@"200------");
break;
case 206:
NSLog(@"206------");
break;
case 304:
NSLog(@"304------");
break;
case 400:
NSLog(@"400------");
break;
case 404:
NSLog(@"404------");
break;
case 416:
NSLog(@"416------");
break;
case 403:
NSLog(@"403------");
break;
case 401:
NSLog(@"401------");
case 500:
NSLog(@"500------");
break;
default:
break;
}
}
服務器模式的流程:
*******最重要的一點:在確認服務端收到receipt之前不要結束訂單(不要調用[[SKPaymentQueue defaultQueue] finishTransaction:transaction];)******
1.app從服務器獲取產品標識列表
2.app從app store 獲取產品信息
3.用戶選擇需要購買的產品
4.app 發送 支付請求到app store
5.app store 處理支付請求,返回transaction信息
6.app 將transaction receipt 發送到服務器
7.服務器收到收據后發送到app stroe驗證收據的有效性
8.app store 返回收據的驗證結果
9.根據app store 返回的結果決定用戶是否購買成功
服務器驗證這樣處理---在下面這個交易結束方法里進行服務器驗證;就不需要上面本地內置模式的代碼塊了
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
NSLog(@"交易結束");
NSString *productID = transaction.payment.productIdentifier;
//驗證購買結果
if (productID.length > 0) {
//向自己的服務器驗證購買憑證
//最好將返回的數據轉換成 base64再傳給后臺,后臺再轉換回來;以防返回字符串中有特字符傳給后臺顯示空
NSString *result=[[NSString alloc]initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
NSLog(@"---transactionReceipt:%@", result);
NSString *environment = [self environmentForReceipt:result];
// appStoreReceiptURL iOS7.0增加的,購買交易完成后,會將憑據存放在該地址
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
// 從沙盒中獲取到購買憑據
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
NSString *sendString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
NSLog(@"_____%@",sendString);
//******這個二進制數據由服務器進行驗證*****************************
NSData *postData = [NSData dataWithBytes:[sendString UTF8String] length:[sendString length]];
//在這里進行服務器驗證
//******本地驗證***********************************************
[self localPaymentTransactionVerify:environment body:postData transaction:transaction];
//結束交易(收到服務器的驗證之后再調用此方法---避免造成漏單)
// [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
}
上述兩種模式的不同之處主要在于:交易的收據驗證,內建模式沒有專門去驗證交易收據,而服務器模式會使用獨立的服務器去驗證交易收據。內建模式簡單快捷,但容易被破解。服務器模式流程相對復雜,但相對安全。
=================2.再說下關于漏單的處理==============
*****最重要的一點:在確認服務端收到receipt之前不要結束訂單(不要調用[[SKPaymentQueue defaultQueue] finishTransaction:transaction];)
漏單:正常玩家購買了卻沒有收到物品、且自己的服務端沒有任何記錄iOS的訂單。iOS的補單是非常麻煩的,用戶提供支付的截圖中的訂單號我們又不能在itunes 或者其他地方找到相應的訂單號。
服務端需要處理一個receipt中攜帶了多個未處理的訂單,即在in-app中有多個支付記錄。
因為雖然按正常邏輯,一次只會處理一筆支付,在漏掉以前充值訂單的情況下,一個receipt,可能含有多個購買記錄,這些記錄可能就是沒有下發給用戶的,需要對receipt 的 in-app記錄逐條檢查,根據訂單記錄查看某一單是否已經下發過了。
如果 in_app 里面值為空.看下這個:https://forums.developer.apple.com/thread/8954