網(wǎng)上有很多內(nèi)購教程,推薦幾篇比較寫的比較好的
文章一: iOS內(nèi)購(IAP,In App Purchases-在APP內(nèi)部支付),設(shè)置及使用
文章二: iOS開發(fā)內(nèi)購全套圖文教程,這篇文章比較老,但是下面的評論可以看看,很有價值
文章三: iOS 內(nèi)購2017.4
1、內(nèi)購集成成功,但是請求到的商品數(shù)是0
// 獲取商品的查詢結(jié)果
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
打印商品的數(shù)量response.products.count結(jié)果一直是零,打印response.invalidProductIdentifiers發(fā)現(xiàn)商品是無效狀態(tài)
解決方法:
可能出現(xiàn)的商品列表為0的情況:
- 1.銀行信息未填寫完整。
- 2.Bundle ID 不統(tǒng)一, bundleID要與iTunes Connect上你App的相同,不然是請求不到產(chǎn)品信息的
2、購買消耗性產(chǎn)品后, 再次購買會提示您已購買此APP內(nèi)購項目,此項目將免費恢復(fù)

解決方法:
監(jiān)聽購買結(jié)果,當(dāng)失敗和成功時代碼中要調(diào)用如下代碼:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
該方法通知蘋果支付隊列該交易已完成,不然就會已發(fā)起相同 ID 的商品購買就會有此項目將免費恢復(fù)的提示。
3、每次重新進(jìn)入選購商品頁面,都會提示之前購買商品后的成功或者失敗提示即再次運行-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions這個方法
解決方法:
在購買成功或者失敗后,沒有調(diào)用
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];告訴蘋果支付隊列,改交易已經(jīng)完成,
在購買成功或者失敗后,加上這句代碼
4、注意事項
在沙盒環(huán)境下真機(jī)測試內(nèi)購時,請去app store中注銷你的apple ID,不然發(fā)起支付購買請求后會直接case:SKPaymentTransactionStateFailed。使用沙盒測試員的賬號時不需要真正花錢的。
5、校驗票據(jù)
票據(jù)的校驗是保證內(nèi)購安全完成的非常關(guān)鍵的一步,一般有三種方式:
1、服務(wù)器驗證,獲取票據(jù)信息后上傳至信任的服務(wù)器,由服務(wù)器完成與App Store的驗證(提倡使用此方法,比較安全)
2、本地票據(jù)校驗
3、本地App Store請求驗證
a)、本地票據(jù)校驗
票據(jù)驗證部分部分可參考此文章
本地票據(jù)校驗的一般步驟
要驗證收據(jù),請按順序執(zhí)行以下測試:
1.找到收據(jù)。
如果沒有收據(jù),則驗證失敗。
2.驗證收據(jù)是否由Apple 正確簽署
如果收據(jù)不是由 Apple 簽署,則驗證失敗。
3.驗證收據(jù)中的Bundle Identifier(數(shù)據(jù)包標(biāo)識符)與在Info.plist文件中含有您要的CFBundleIdentifier值的硬編碼常量相匹配。
如果兩者不匹配,則驗證失敗。
4.驗證收據(jù)中的版本標(biāo)識符字符串與在Info.plist文件中含有您要的CFBundleShortVersionString值(macOS)或
CFBundleVersion 值(iOS)的硬編碼常量相匹配。
如果兩者不匹配,則驗證失敗。
5.按照“計算 GUID 的哈希(Hash)(第 8 頁)”所述計算GUID 的哈希(Hash)。
如果結(jié)果與收據(jù)中的哈希(Hash)不匹配,則驗證失敗。
如果通過所有測試,則驗證成功。
注意: Bundle Identifier(數(shù)據(jù)包標(biāo)識符)和版本標(biāo)識符字符串是UTF-8 字符串,而不僅僅是一系列字節(jié)。確保您相應(yīng)地編寫比較邏輯代碼。
如果您的 App 支持“批量購買計劃”,請檢查收據(jù)的有效日期。
b)、App Store驗證:
// 14.交易成功,與服務(wù)器比對
// 交易結(jié)束,當(dāng)交易結(jié)束后還要去appstore上驗證支付信息是否都正確,只有所有都正確后,我們就可以給用戶發(fā)放我們的虛擬物品了。
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
NSString * str=[[NSString alloc]initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
NSString *environment = [self environmentForReceipt:str]; // 判斷當(dāng)前的環(huán)境(正式環(huán)境還是測試環(huán)境)
NSLog(@"environment:%@",environment);
// 驗證憑據(jù),獲取到蘋果返回的交易憑據(jù)
// appStoreReceiptURL iOS7.0增加的,購買交易完成后,會將憑據(jù)存放在該地址,目前蘋果公司提倡的獲取購買憑證的方法
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
// 從沙盒中獲取到購買的票據(jù)信息
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
/**
BASE64 常用的編碼方案,通常用于數(shù)據(jù)傳輸,以及加密算法的基礎(chǔ)算法,傳輸過程中能夠保證數(shù)據(jù)傳輸?shù)姆€(wěn)定性
BASE64是可以編碼和解碼的
base64位的產(chǎn)品驗證碼單,base64是服務(wù)端和蘋果進(jìn)行校驗所必須的,蘋果的文檔要求憑證經(jīng)過Base64加密
*/
NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
NSString *sendString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
NSURL *StoreURL=nil;
// 沙盒狀態(tài)下使用:https://sandbox.itunes.apple.com/verifyReceipt來驗證
// 生產(chǎn)環(huán)境下使用:https://buy.itunes.apple.com/verifyReceipt
if ([environment isEqualToString:@"environment = Sandbox"]) {
StoreURL= [[NSURL alloc] initWithString: @"https://sandbox.itunes.apple.com/verifyReceipt"];
}
else{
StoreURL= [[NSURL alloc] initWithString: @"https://buy.itunes.apple.com/verifyReceipt"];
}
//這個二進(jìn)制數(shù)據(jù)由服務(wù)器進(jìn)行驗證;zl
NSData *postData = [NSData dataWithBytes:[sendString UTF8String] length:[sendString length]];
NSMutableURLRequest *connectionRequest = [NSMutableURLRequest requestWithURL:StoreURL];
[connectionRequest setHTTPMethod:@"POST"];
[connectionRequest setTimeoutInterval:50.0];//120.0---50.0zl
[connectionRequest setCachePolicy:NSURLRequestUseProtocolCachePolicy];
[connectionRequest setHTTPBody:postData];
//開始請求
NSError *error=nil;
NSData *responseData=[NSURLConnection sendSynchronousRequest:connectionRequest returningResponse:nil error:&error];
if (error) {
NSLog(@"驗證購買過程中發(fā)生錯誤,錯誤信息:%@",error.localizedDescription);
return;
}
NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
/*
* 這里可以等待上面請求的數(shù)據(jù)完成后并且state = 0 驗證憑據(jù)成功來判斷后進(jìn)入自己服務(wù)器邏輯的判斷,也可以直接進(jìn)行服務(wù)器邏輯的判斷,驗證憑據(jù)也就是一個安全的問題。樓主這里沒有用state = 0 來判斷。
* 完整結(jié)束此次在App Store的交易,沒有這句代碼的調(diào)用,下次購買會提示已經(jīng)購買該商品
* [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
*/
NSLog(@"請求成功后的數(shù)據(jù):%@",dic);
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);
//這里可以做操作吧用戶對應(yīng)的虛擬物品通過自己服務(wù)器進(jìn)行下發(fā)操作,或者在這里通過判斷得到用戶將會得到多少虛擬物品,在后面([self getApplePayDataToServerRequsetWith:transaction];的地方)上傳上面自己的服務(wù)器。
}
}
//此方法為將這一次操作上傳給我本地服務(wù)器,記得在上傳成功過后一定要記得銷毀本次操作。調(diào)用[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
// [self getApplePayDataToServerRequsetWith:transaction];
}
-(NSString *)environmentForReceipt:(NSString *)str
{
str = [str stringByReplacingOccurrencesOfString:@"\r\n" withString:@""];
str = [str stringByReplacingOccurrencesOfString:@"\n" withString:@""];
str = [str stringByReplacingOccurrencesOfString:@"\t" withString:@""];
str = [str stringByReplacingOccurrencesOfString: @"" withString:@""];
str = [str stringByReplacingOccurrencesOfString:@"\"" withString:@""];
NSArray *arr = [str componentsSeparatedByString:@";"];
// 存儲收據(jù)環(huán)境的變量
if (arr.count > 2) {
NSString *environment = arr[2];
return environment;
}
return nil;
}
注意:
獲取到票據(jù)以后我們通過App Store來驗證票據(jù)是否真實
沙盒狀態(tài)下使用:https://sandbox.itunes.apple.com/verifyReceipt來驗證
生產(chǎn)環(huán)境下使用:https://buy.itunes.apple.com/verifyReceipt
附常見的驗證狀態(tài)代碼:

如果此時未獲取到票據(jù)的信息,使用SKReceiptRefreshRequest來刷新票據(jù)結(jié)果。
SKReceiptRefreshRequest *refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}];
refreshReceiptRequest.delegate = self;
[refreshReceiptRequest start];