本文給大家介紹下內(nèi)購,部分地方是摘自網(wǎng)絡(luò)。
創(chuàng)建測試App
首先你需要登錄App的ItunesConnection,你會看到如下界面
點(diǎn)擊新建App,會出現(xiàn)新建窗口;

在之而立有幾個(gè)需要填寫的地方,名稱、平臺、語言簡體中文,套裝ID也就是Bundle Identifier,需要你在Certificates頁面 申請BundleID,SKU可以理解為用戶看到的唯一標(biāo)示,會體現(xiàn)在你的app的App Store的連接中。
二、添加內(nèi)購
App創(chuàng)建好之后,我們打開創(chuàng)建的App,在左上角選擇功能,會看到左側(cè)的App 內(nèi)購買項(xiàng)目。我們點(diǎn)擊右下角的加號,為App添加內(nèi)購項(xiàng)目。

之后我們會看到類型的選項(xiàng),如下圖

- 消耗型項(xiàng)目 就像你玩游戲需要買金幣,買鉆石等,只要花錢就可以無限次的購買
-
非消耗型項(xiàng)目 就像你在App Store購買App,買了一次之后就不用再買第二次,你擁有永久使用權(quán)。
在我們的app中,是充值會員,所以選擇的是第一種,可以無限次購買
這里有幾個(gè)選項(xiàng),需要填寫商品名稱,產(chǎn)品ID以及價(jià)格等級,簡單說明一下
- 商品名稱根據(jù)你的消費(fèi)道具的實(shí)際意義來說明,比如“100顆寶石”,“100金幣”等。
- 產(chǎn)品ID是比較重要的,由項(xiàng)目自定義,只要唯一即可,因?yàn)闇y試,我在這里隨便填寫的123,在實(shí)際應(yīng)用中,一定要認(rèn)真填寫。
-
價(jià)格等級的話“查看價(jià)格表”中有對應(yīng)的說明,可以對照著表中每個(gè)國家的貨幣價(jià)格與等級來選擇
接下來是語言選擇,和上傳快照如下圖

審核備注,根據(jù)實(shí)際情況填寫,可以不填。而下面的屏幕快照,則是商品圖片,以像素為單位最低尺寸為321,390,尺寸需要如下圖,上傳即可。

到這里為止,我們的內(nèi)購項(xiàng)目則添加完成。接下來則是測試階段了
三、申請沙盒測試賬號(用來測試購買項(xiàng)目)
這個(gè)賬號,是利用蘋果的沙盒測試環(huán)境來模擬AppStore的購買流程
首先我們回到iTunes Connect中,在這里我們選擇用戶和職能

然后再上面的第三個(gè)選項(xiàng)沙盒技術(shù)測試員中點(diǎn)擊?,添加測試員。

- 在信息填寫頁面只簡單說兩句。
- 所有信息都可以隨意填寫,不用管是否真實(shí)。
- App Store地區(qū)選擇,一點(diǎn)要選對,它對應(yīng)的是你創(chuàng)建的App的地區(qū),你App是中國的話,在這里我們依然選擇中國。
- 此賬號只能用來測試,不要在正式的appstore上使用
-
填寫完畢,點(diǎn)擊保存后,我們則生成一個(gè)測試賬號,當(dāng)然這個(gè)賬號是可以隨時(shí)刪除和添加的。
之后就是寫代碼了。。。
- (IBAction)buy50:(id)sender {
[self buy:IAP0p20];
}
- (void)buy:(int)type {
buyType = type;
if ([SKPaymentQueue canMakePayments]) {
[self requestProductData];
NSLog(@"****************允許程序內(nèi)購********************");
} else {
NSLog(@"****************不允許程序內(nèi)購********************");
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"您的手機(jī)沒有打開程序內(nèi)付費(fèi)購買" delegate:nil cancelButtonTitle:@"關(guān)閉" otherButtonTitles:nil, nil];
[alertView show];
}
}
- (void)requestProductData {
NSLog(@"****************請求對應(yīng)的產(chǎn)品信息********************");
NSArray *products = nil;
switch (buyType) {
case IAP0p20:
{
currentProductId = ProductID_IAP0p20;
products = [[NSArray alloc] initWithObjects:ProductID_IAP0p20, nil];
}
break;
default:
break;
}
NSSet *productSet = [NSSet setWithArray:products];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:productSet];
request.delegate = self;
[request start];
}
#pragma mark - SKProductsRequestDelegate
// 請求協(xié)議//收到的產(chǎn)品信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSLog(@"****************收到產(chǎn)品反饋信息********************");
NSArray *myProduct = response.products;
if (myProduct.count == 0) {
NSLog(@"****************沒有商品********************");
return;
}
NSLog(@"productID:%@", response.invalidProductIdentifiers);
NSLog(@"產(chǎn)品付費(fèi)數(shù)量:%lu", (unsigned long)[myProduct count]);
SKProduct *p = nil;
for (SKProduct *product in myProduct) {
if ([product.productIdentifier isEqualToString:currentProductId]) {
p = product;
}
}
SKPayment *payment = [SKPayment paymentWithProduct:p];
NSLog(@"****************發(fā)送購買請求********************");
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(@"****************請求失敗********************");
}
- (void)requestDidFinish:(SKRequest *)request {
NSLog(@"****************反饋信息結(jié)束********************");
}
#pragma mark - 監(jiān)聽購買結(jié)果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
for(SKPaymentTransaction *tran in transactions){
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased:{
NSLog(@"交易完成");
[self verifyPurchaseWithPaymentTransaction];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
case SKPaymentTransactionStatePurchasing:
NSLog(@"商品添加進(jìn)列表");
break;
case SKPaymentTransactionStateRestored:{
NSLog(@"已經(jīng)購買過商品");
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
case SKPaymentTransactionStateFailed:{
NSLog(@"交易失敗");
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
default:
break;
}
}
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"交易結(jié)束");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
// 沙盒測試環(huán)境驗(yàn)證
#define SANDBOX @"https://sandbox.itunes.apple.com/verifyReceipt"
// 正式環(huán)境驗(yàn)證
#define AppStore @"https://buy.itunes.apple.com/verifyReceipt"
- (void)verifyPurchaseWithPaymentTransaction {
// 從沙盒中獲取交易憑證并且拼接成請求體數(shù)據(jù)
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//轉(zhuǎn)化為base64字符串
NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接請求數(shù)據(jù)
NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
//創(chuàng)建請求到蘋果官方進(jìn)行購買驗(yàn)證
NSURL *url=[NSURL URLWithString:SANDBOX];
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(@"驗(yàn)證購買過程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
return;
}
NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
NSLog(@"%@",dic);
if([dic[@"status"] intValue]==0){
NSLog(@"購買成功!");
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:currentProductId]) {
int purchasedCount=[defaults integerForKey:productIdentifier];//已購買數(shù)量
[[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier];
}else{
[defaults setBool:YES forKey:productIdentifier];
}
//在此處對購買記錄進(jìn)行存儲,可以存儲到開發(fā)商的服務(wù)器端
}else{
NSLog(@"購買失敗,未通過驗(yàn)證!");
}
}
#pragma mark - 監(jiān)聽釋放
- (void)dealloc {
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
在這里需要注意幾點(diǎn),
- 代碼中的_currentProId所填寫的是你的購買項(xiàng)目的的ID,這個(gè)和第二步創(chuàng)建的內(nèi)購的productID要一致;本例中是 123。
- 在監(jiān)聽購買結(jié)果后,一定要調(diào)用[[SKPaymentQueue defaultQueue] finishTransaction:tran];來允許你從支付隊(duì)列中移除交易。
- 沙盒環(huán)境測試appStore內(nèi)購流程的時(shí)候,請使用沒越獄的設(shè)備。
- 請務(wù)必使用真機(jī)來測試,一切以真機(jī)為準(zhǔn)。
- 項(xiàng)目的Bundle identifier需要與您申請AppID時(shí)填寫的bundleID一致,不然會無法請求到商品信息。
- 真機(jī)測試的時(shí)候,一定要退出原來的賬號,才能用沙盒測試賬號
- 二次驗(yàn)證,請注意區(qū)分宏, 測試用沙盒驗(yàn)證,App Store審核的時(shí)候也使用的是沙盒購買,所以驗(yàn)證購買憑證的時(shí)候需要判斷返回Status Code決定是否去沙盒進(jìn)行二次驗(yàn)證,為了線上用戶的使用,驗(yàn)證的順序肯定是先驗(yàn)證正式環(huán)境,此時(shí)若返回值為21007,就需要去沙盒二次驗(yàn)證,因?yàn)榇速徺I的是在沙盒進(jìn)行的。


