[iOS]應用內支付(內購)的個人開發過程及坑! – iPhone手機開發 iPhone軟體開發教學課程

本文會給大傢詳細介紹iOS內購,這是本人16年5月底的開發過程,希望對看完此篇文章的人有所幫助。
本文基於XcodeVersion 7.3 (7D175)版本,手機是iPhone 6,9.3系統。
部分地方直接摘自網絡,基本上是我的邏輯,省時省心省力。

一. 創建測試App

首先你需要登錄 App的ItunesConnection,你會看到如下界面
這裡寫圖片描述vcPmyv2+3aOswO/D5tPQx/rP3828tcjNvM7EveG6z7XEt73Kvbj4ztLDx7LOv7yhozxiciAvPg0KMy64tr/uus2yxs7xsai45s/Uyr61xMrHxOO1xMrVyOvS1LywuLa/7rXIz+C52NDFz6KhozxiciAvPg0KNC5pQWTW99Kqyse4+rnjuObT0LnYo6y/qrei1d+/ydLUtcfCvLW9V29ya2JlbmNoo6zNqLn9aUFkttTTptPDtcS547jmvfjQ0L/Y1sahozxiciAvPg0KNS7Tw7unus3WsMTc08PT2sn6s8nP4NOm1cu6xaOswP3I58a7ufvJs7rTsuLK1NXLusWhozxiciAvPg0KNi7QrdLpo6zLsM7xus3S+NDQ0rXO8dTyysfE49L40NDP4LnY1cu7p7XE0MXPosno1sOhozxiciAvPg0K1NrV4sDvztLDx9Gh1PG12tK7uPbRoc/uo6zO0rXEQXBwo6wgyLu687Xju/fX88nPvce1xLzTusWjrNDCvajSu7j208PAtLLiytTTw7XEQXBwoaM8YnIgLz4NCjxpbWcgYWx0PQ==”這裡寫圖片描述” src=”/uploadfile/Collfiles/20160531/20160531090500345.png” title=”\” />

點新建 App,會出現新建窗口;
這裡寫圖片描述

在這裡有幾個需要填寫的地方,名稱自己取,平臺IOS,語言選擇瞭簡體中文,套裝ID也就是你的Bundle Identifier,需要你在Certificates頁面 申請BundleID,SKU可以理解為用戶看一看到的唯一標示,會體現在你的app的App Store的鏈接中。

二.添加內購

App創建好之後,我們打開創建的App,在左上角選擇功能,會看到左側的App 內購買項目。我們點擊右下角的加號,為App添加內購項目。
這裡寫圖片描述

之後我們會看到類型的選項,如下圖

這裡寫圖片描述

官方的註釋寫的很清楚瞭,隻在這裡簡單的說下前兩種:
– 消耗型項目 就像你玩遊戲需要買金幣,買鉆石等,隻要花錢就可以無限次的購買
– 非消耗型項目 就像你在App Store購買App,買瞭一次之後就不用再買第二次,你擁有永久使用權。
在我們的app中,是充值會員,所以選擇的是第一種,可以無限次購買。

這裡寫圖片描述

這裡有幾個選項,需要填寫商品名稱,產品ID以及價格等級,簡單說明一下
1. 商品名稱根據你的消費道具的實際意義來說明,比如“100顆寶石”,“100金幣”等。
2. 產品ID是比較重要的,由項目自定義,隻要唯一即可,因為測試,我在這裡隨便填寫的123,在實際應用中,一定要認真填寫。
3. 價格等級的話“查看價格表”中有對應的說明,可以對照著表中每個國傢的貨幣價格與等級來選擇
接下來是語言選擇,和上傳快照如下圖

這裡寫圖片描述

點擊添加語言,填寫名稱和描述,這裡我們依然選擇簡體中文,如下

這裡寫圖片描述

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

這裡寫圖片描述

到這裡為止, 我們的內購項目則添加完成。接下來則是測試階段瞭。

三.申請沙盒測試賬號(用來測試購買項目)

這個賬號,是利用蘋果的沙盒測試環境來模擬AppStore的購買流程,你肯定不會想要用真實RMB去購買測試吧?
首先我們回到iTunes Connect中,在這裡我們選擇用戶和職能。

這裡寫圖片描述

然後在上面的第三個選項沙箱技術測試員中點擊加號,添加測試員。

這裡寫圖片描述

在信息填寫頁面隻簡單說兩句。
所有信息都可以隨意填寫,不用管是否真實。
App Store地區選擇,一定要選對,它對應的是你創建的App的地區, 你App是中國的話, 在這裡我們依然選擇中國。
此賬號隻能用來測試,不要在正式的appstore上使用
填寫完畢,點擊保存後,我們則生成一個測試賬號,當然這個賬號是可以隨時刪除和添加的。

這裡寫圖片描述

之後終於到瞭寫代碼的時候瞭,點開你的Xcode創建你的項目!
大部分代碼都可以在.m文件中實現。

#import "ViewController.h"
#import 
#import "SVProgressHUD.h"

@interface ViewController ()
@property (nonatomic,copy) NSString *currentProId;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake(100, 100, 100, 100);
    button.backgroundColor = [UIColor greenColor];
    [button setTitle:@"6元" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchDown];
    [self.view addSubview:button];
}


- (void)btnClick:(UIButton *)button
{
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    _currentProId = @"123";
    if([SKPaymentQueue canMakePayments]){
        [self requestProductData:product];
    }else{
        NSLog(@"不允許程序內付費");
    }
}

//去蘋果服務器請求商品
- (void)requestProductData:(NSString *)type{
    NSLog(@"-------------請求對應的產品信息----------------");

    [SVProgressHUD showWithStatus:nil maskType:SVProgressHUDMaskTypeBlack];

    NSArray *product = [[NSArray alloc] initWithObjects:type,nil];

    NSSet *nsset = [NSSet setWithArray:product];
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
    request.delegate = self;
    [request start];

}

//收到產品返回信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{

    NSLog(@"--------------收到產品反饋消息---------------------");
    NSArray *product = response.products;
    if([product count] == 0){
        [SVProgressHUD dismiss];
        NSLog(@"--------------沒有商品------------------");
        return;
    }

    NSLog(@"productID:%@", response.invalidProductIdentifiers);
    NSLog(@"產品付費數量:%lu",(unsigned long)[product count]);

    SKProduct *p = nil;
    for (SKProduct *pro in product) {
        NSLog(@"%@", [pro description]);
        NSLog(@"%@", [pro localizedTitle]);
        NSLog(@"%@", [pro localizedDescription]);
        NSLog(@"%@", [pro price]);
        NSLog(@"%@", [pro productIdentifier]);

        if([pro.productIdentifier isEqualToString:_currentProId]){
            p = pro;
        }
    }

    SKPayment *payment = [SKPayment paymentWithProduct:p];

    NSLog(@"發送購買請求");
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

//請求失敗
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
    [SVProgressHUD showErrorWithStatus:@"支付失敗"];
    NSLog(@"------------------錯誤-----------------:%@", error);
}

- (void)requestDidFinish:(SKRequest *)request{
    [SVProgressHUD dismiss];
    NSLog(@"------------反饋信息結束-----------------");
}
//沙盒測試環境驗證
#define SANDBOX @"https://sandbox.itunes.apple.com/verifyReceipt"
//正式環境驗證
#define AppStore @"https://buy.itunes.apple.com/verifyReceipt"
/**
 *  驗證購買,避免越獄軟件模擬蘋果請求達到非法購買問題
 *
 */
-(void)verifyPurchaseWithPaymentTransaction{
    //從沙盒中獲取交易憑證並且拼接成請求體數據
    NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl];

    NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//轉化為base64字符串

    NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接請求數據
    NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];


    //創建請求到蘋果官方進行購買驗證
    NSURL *url=[NSURL URLWithString:SANDBOX];
    NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url];
    requestM.HTTPBody=bodyData;
    requestM.HTTPMethod=@"POST";
    //創建連接並發送同步請求
    NSError *error=nil;
    NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error];
    if (error) {
        NSLog(@"驗證購買過程中發生錯誤,錯誤信息:%@",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"];//讀取產品標識
        //如果是消耗品則記錄購買數量,非消耗品則記錄是否購買過
        NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
        if ([productIdentifier isEqualToString:@"123"]) {
            int purchasedCount=[defaults integerForKey:productIdentifier];//已購買數量
            [[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier];
        }else{
            [defaults setBool:YES forKey:productIdentifier];
        }
        //在此處對購買記錄進行存儲,可以存儲到開發商的服務器端
    }else{
        NSLog(@"購買失敗,未通過驗證!");
    }
}
//監聽購買結果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
    for(SKPaymentTransaction *tran in transaction){
        switch (tran.transactionState) {
            case SKPaymentTransactionStatePurchased:{
                NSLog(@"交易完成");
                // 發送到蘋果服務器驗證憑證
                [self verifyPurchaseWithPaymentTransaction];
                [[SKPaymentQueue defaultQueue] finishTransaction:tran];

            }
                break;
            case SKPaymentTransactionStatePurchasing:
                NSLog(@"商品添加進列表");

                break;
            case SKPaymentTransactionStateRestored:{
                NSLog(@"已經購買過商品");

                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
            }
                break;
            case SKPaymentTransactionStateFailed:{
                NSLog(@"交易失敗");
                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                [SVProgressHUD showErrorWithStatus:@"購買失敗"];
            }
                break;
            default:
                break;
        }
    }
}

//交易結束
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
    NSLog(@"交易結束");

    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}


- (void)dealloc{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

在這裡需要註意幾點,
1. 代碼中的_currentProId所填寫的是你的購買項目的的ID,這個和第二步創建的內購的productID要一致;本例中是 123。
2. 在監聽購買結果後,一定要調用[[SKPaymentQueue defaultQueue] finishTransaction:tran];來允許你從支付隊列中移除交易。
3. 沙盒環境測試appStore內購流程的時候,請使用沒越獄的設備。
4. 請務必使用真機來測試,一切以真機為準。
5. 項目的Bundle identifier需要與您申請AppID時填寫的bundleID一致,不然會無法請求到商品信息。
6. 真機測試的時候,一定要退出原來的賬號,才能用沙盒測試賬號
7. 二次驗證,請註意區分宏, 測試用沙盒驗證,App Store審核的時候也使用的是沙盒購買,所以驗證購買憑證的時候需要判斷返回Status Code決定是否去沙盒進行二次驗證,為瞭線上用戶的使用,驗證的順序肯定是先驗證正式環境,此時若返回值為21007,就需要去沙盒二次驗證,因為此購買的是在沙盒進行的。

附:蘋果支付錯誤目錄

Status Code Description
21000 The App Store could not read the JSON object you provided.
21002 The data in the receipt-data property was malformed or missing.
21003 The receipt could not be authenticated.
21004 The shared secret you provided does not match the shared secret on file for your account.Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.
21005 The receipt server is not currently available.
21006 This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.
21007 This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.
21008 This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.

參考
    蘋果官方文檔

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *