最近公司項目要增加內(nèi)購功能,摸索了幾天,遇到不少問題,先記錄下來,有圖有真相,我盡量多貼些圖片。
1、首先你要有一個app,無論上架與否都可以,如果沒有,就新建一個,怎么新建APP就不多說了,有一條要注意,APP的APPID信息里一定要支持In-App Purchases。

2、進(jìn)入iTunes Connect 選擇我的APP,在APP的詳情頁,選擇功能選項,添加APP內(nèi)要出售的物品

添加物品的時候會讓你選擇物品類型,按你的需求選擇就行了,有幾個商品就添加幾個,

注意,物品的資料要填寫完整,必填項一定要填,

審核信息里的圖片要上傳,備注也要寫上,

3、當(dāng)你的物品狀態(tài)是這樣的,就說明可以在代碼里獲取物品了,

當(dāng)然,這時候你要直接寫代碼獲取,肯定是獲取不到的,因為還有一些非APP信息需要補充,想當(dāng)初我就是沒逼著產(chǎn)品完善信息,自己一直找錯誤,浪費了許多時間。
協(xié)議、稅務(wù)和銀行業(yè)務(wù),這里面的信息怎么填寫,這篇文章寫的很詳細(xì),就不多說了,讓產(chǎn)品參考著寫吧。

4、為了測試支付情況,我們還需要新建一個測試用的Apple id,在iTunes Connect里選擇用戶和職能

選擇沙箱技術(shù)測試員,添加測試賬號,可以參考這篇文章。
5,以上都完成之后,終于可以打開編譯器了,對,打開xcode,
打開Xcode 的這個功能。

6,寫代碼吧,
引入#import <StoreKit/StoreKit.h>
遵守協(xié)議

添加監(jiān)聽
[[SKPaymentQueue defaultQueue]addTransactionObserver:self];
(別忘了移除監(jiān)聽)
請求物品列表可以從自己的服務(wù)器,也可以從蘋果的服務(wù)器請求,但是從蘋果請求的話,慢慢等吧,很慢的,而且不穩(wěn)定。
下面的代碼是從點擊物品開始,直到完成付款并通過自己的服務(wù)器驗證。
//點擊購買按鈕- (void)clickPurcaseBtnAction{? ?
?//點擊按鈕的時候判斷app是否允許apple支付??
? if ([SKPaymentQueue canMakePayments]) {? ? ?
?? //請求蘋果后臺商品? ? ?
?? [self requestProductData:_productId];? ?
?}??
? else? ? {? ? ??
? NSLog(@"用戶不允許應(yīng)用內(nèi)購買");??
? }}
//去蘋果服務(wù)器請求商品
-(void)requestProductData:(NSString *)type {??
? //根據(jù)商品ID查找商品信息??
? NSArray *product = [[NSArray alloc] initWithObjects:type, nil];? ?
?NSSet *nsset = [NSSet setWithArray:product];? ?
?//創(chuàng)建SKProductsRequest對象,用想要出售的商品的標(biāo)識來初始化,然后附加上對應(yīng)的委托對象。? ?
?//該請求的響應(yīng)包含了可用商品的本地化信息。??
? SKProductsRequest *request = [[SKProductsRequest alloc]? initWithProductIdentifiers:nsset];? ??
request.delegate = self;? ?
?[request start];
}
// SKProductsRequestDelegate
//查詢成功的回調(diào)
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:? (SKProductsResponse *)response {? ??
NSArray *product = response.products;? ??
//invalidProductIdentifiers是不被App Store所識別的產(chǎn)品id字符串?dāng)?shù)組,通常為空? ?
?NSLog(@"產(chǎn)品Product ID:%@",response.invalidProductIdentifiers);? ?
?//如果沒應(yīng)用的銀行信息沒填寫完整,你添加的商品會出現(xiàn)在無效商品列表里? ?
?NSLog(@"無效商品列表 :%@",response.invalidProductIdentifiers); ??
?if(product == nil) {??
? ? ? return;??
? }? ??
//數(shù)組的count代表回調(diào)的產(chǎn)品ID數(shù)組的長度? ?
?if (product.count == 0) {? ? ??
? NSLog(@"無法獲得產(chǎn)品信息,購買失敗"); ??
? ? ? ? return; ??
?}? ?
?//SKProduct對象包含了在App Store上注冊的商品的本地化信息。? ?
?SKProduct *storeProduct = nil;??
? for (SKProduct *pro in product) {? ? ?
?? if ([pro.productIdentifier isEqualToString:_productId]) {? ? ? ??
? ? storeProduct = pro;? ? ?
?? }? ?
?}? ?
?if(storeProduct == nil) {? ? ?
?? return;??
? }? ??
//創(chuàng)建一個支付對象,并放到隊列中? ??
self.g_payment = [SKMutablePayment paymentWithProduct:storeProduct];? ?
?//設(shè)置購買的數(shù)量? ?
?self.g_payment.quantity = 1;??
? [[SKPaymentQueue defaultQueue] addPayment:self.g_payment];
}
//查詢失敗的回調(diào)
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {? ?
?NSLog(@"請求商品失敗%@", error);
}
//交易有結(jié)果時會調(diào)用此回調(diào)函數(shù),SKPaymentTransactionObserver - 交易的回調(diào)
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray*)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing: //正在購買
break;
case SKPaymentTransactionStatePurchased:? //購買成功
[self completedTransaction:transaction Product:self.productId];
break;
case SKPaymentTransactionStateFailed://購買失敗
{
UIAlertView *alt3 = [[UIAlertView alloc]initWithTitle:@"提示" message:@"購買失敗" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"確定", nil];
[alt3 show];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
break;
case SKPaymentTransactionStateRestored:? //恢復(fù)購買
{
//當(dāng)上次購買過程中,因某種原因以外中斷而沒能調(diào)用完成購買的API時,再次進(jìn)入APP蘋果會提醒恢復(fù)購買,可以在這里繼續(xù)處理
}
break;
case SKPaymentTransactionStateDeferred:? //最終狀態(tài)未確認(rèn)
{
UIAlertView *alt5 = [[UIAlertView alloc]initWithTitle:@"提示" message:@"無法獲得產(chǎn)品信息,購買失敗" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"確定", nil];
[alt5 show];
}
break;
default:
break;
}
}
}
-(void)completedTransaction:(SKPaymentTransaction*)transaction Product:(NSString *)productId{
//NSString * productIdentifier = transaction.payment.productIdentifier;
NSString *transactionReceiptString= nil;
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
把transactionReceiptString發(fā)送給自己的服務(wù)器讓后臺驗證就行了,還有一點就是對于sandbox環(huán)境和生產(chǎn)環(huán)境,校驗的地址是不同的,后臺校驗的時候要區(qū)分測試和生產(chǎn)環(huán)境,注意:提交蘋果審核后,審核的時候蘋果用的也是測試環(huán)境,后臺去驗證的時候需要在測試環(huán)境驗證,過審后再去生產(chǎn)環(huán)境驗證。
https://buy.itunes.apple.com/verifyReceipt (生產(chǎn))
https://sandbox.itunes.apple.com/verifyReceipt (sandbox測試)
/* 下面的代碼是客戶端自己驗證的方法
NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", transactionReceiptString];//拼接請求數(shù)據(jù)
NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
NSURL *url=[NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url];
requestM.HTTPBody = bodyData;
requestM.HTTPMethod=@"POST";
//創(chuàng)建連接并發(fā)送同步請求
NSError *error=nil;
NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error];
if (error) {
NSLog(@"驗證購買過程中發(fā)生錯誤,錯誤信息:%@",error.localizedDescription);
return;
}
NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
NSLog(@"%@",dic);
if([dic[@"status"] intValue]==0){
[[UIApplication sharedApplication].keyWindow makeToast:@"購買成功" duration:1.5 position:CSToastPositionCenter];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSDictionary *dicReceipt= dic[@"receipt"];
NSDictionary *dicInApp=[dicReceipt[@"in_app"] firstObject];
NSString *productIdentifier= dicInApp[@"product_id"];//讀取產(chǎn)品標(biāo)識
//如果是消耗品則記錄購買數(shù)量,非消耗品則記錄是否購買過
//NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
if ([productIdentifier isEqualToString:@"123"]) {
//? ? ? ? ? ? NSInteger purchasedCount=[defaults integerForKey:productIdentifier];//已購買數(shù)量
//? ? ? ? ? ? [[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier];
}else{
//? [defaults setBool:YES forKey:productIdentifier];
}
//在此處對購買記錄進(jìn)行存儲,可以存儲到開發(fā)商的服務(wù)器端
}else{
NSLog(@"購買失敗,未通過驗證");
NSString *str = [NSString stringWithFormat:@"失敗了%@",dic[@"status"]];
[[UIApplication sharedApplication].keyWindow makeToast:str duration:1.5 position:CSToastPositionCenter];
}
*/
}
遇到的問題
1、銀行信息不完整造成的無法獲取商品列表
2、蘋果服務(wù)器慢,網(wǎng)絡(luò)問題等造成的購買失敗
3、交給服務(wù)器驗證的字符串,服務(wù)器需要再進(jìn)行UTF8編碼一次再去驗證,否則會返回21002,收據(jù)數(shù)據(jù)不符合格式。
4、順便注上蘋果反饋的錯誤碼:
21000App Store無法讀取你提供的JSON數(shù)據(jù)
21002 收據(jù)數(shù)據(jù)不符合格式
21003 收據(jù)無法被驗證
21004 你提供的共享密鑰和賬戶的共享密鑰不一致
21005 收據(jù)服務(wù)器當(dāng)前不可用
21006 收據(jù)是有效的,但訂閱服務(wù)已經(jīng)過期。當(dāng)收到這個信息時,解碼后的收據(jù)信息也包含在返回內(nèi)容中
21007 收據(jù)信息是測試用(sandbox),但卻被發(fā)送到產(chǎn)品環(huán)境中驗證
21008 收據(jù)信息是產(chǎn)品環(huán)境中使用,但卻被發(fā)送到測試環(huán)境中驗證
現(xiàn)在代碼還比較散亂,只是實現(xiàn)了功能,后面準(zhǔn)備封裝一下,遇到問題再來補充吧~