iOS應用內支付(內購)的個人開發過程及坑!


本文會給大家詳細介紹iOS內購,這是本人16年5月底的開發過程,希望對看完此篇文章的人有所幫助。

 

本文基於XcodeVersion 7.3 (7D175)版本,手機是iPhone 6,9.3系統。

 

部分地方直接摘自網絡,基本上是我的邏輯,省時省心省力。

 

一. 創建測試App

 

首先你需要登錄 App的ItunesConnection,你會看到如下界面

 

 

簡單的介紹一下這幾個選項

 

1.我的App主要用於管理自己的App應用,例如編輯資料,上架,下架等。
2.銷售和趨勢主要是來查看App在各個平台的下載量,收入等方面數據,里面有曲線圖等圖文結合的方式給我們參考。
3.付款和財務報告顯示的是你的收入以及付款等相關信息。
4.iAd主要是跟廣告有關,開發者可以登錄到Workbench,通過iAd對應用的廣告進行控制。
5.用戶和職能用於生成相應賬號,例如蘋果沙河測試賬號。
6.協議,稅務和銀行業務則是你銀行相關賬戶的信息設置。
在這里我們選擇第一個選項,我的App, 然后點擊左上角的加號,新建一個用來測試用的App。

 

點新建 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 <StoreKit/StoreKit.h>
#import "SVProgressHUD.h"

@interface ViewController ()<SKPaymentTransactionObserver,SKProductsRequestDelegate>
@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,就需要去沙盒二次驗證,因為此購買的是在沙盒進行的。

附:蘋果支付錯誤目錄

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM