iOS 內購開發

一.設置協議

1.打開App Store Connect,選擇 協議,稅務和銀行業務
協議,稅務和銀行業務.png
2.因為我已經添加過信息了,所有展示內容是這樣的
協議.png
3.如果想知道要怎么設置相關信息的話 請參考

iOS內購(IAP,In App Purchases-在APP內部支付),設置及使用

4.這是在協議、稅務和銀行業務頁面頁面添加銀行賬戶時遇到的問題

4-1. 國內銀行CNAPS CODE 查詢
4-2. 在提交信息的時候提示 : 賬戶持有人姓名無效. 持卡人姓名,中文名用拼寫,名在前,姓在后 例:Xiaoer Wang

二.添加內購商品

1.打開App Store Connect,- 我的APP,然后再點擊你要添加內購的APP - 功能 - App內購項目

打開頁面如下圖,已填寫好完整數據的商品,狀態為 (準備提交),未準備好的商品 狀態為 (元數據丟失)


我的APP.png
2. 點擊 (+) 開始創建App內購項目

根據需求來創建內購項目,如果不知道該創建什么類型的話,點擊左下角 進一步了解App內購項

創建APP內購項目.png

3.填寫商品信息

填寫結束后點擊右上角的存儲


上.png

下.png
4.在準備提交的版本中添加APP內購項目

選擇完成后點擊右上角的存儲 準備提交審核


添加APP內購項目.png

三.添加測試賬號來進行測試

1.打開App Store Connect, 點擊 用戶和訪問

打開頁面如下,點擊 (+) 進行創建測試用戶


用戶和訪問.png
2.添加測試員
新測試員.png

四.代碼集成

注意:

1.必須用真機測試。
2.測試的時候必須退出自己的apple ID。彈出頁面后登陸沙盒的測試apple id。

代碼繼承:

1.新建一個名為IAPManager的文件 繼承自NSObject , 文件如下:
//
//  IAPManager.h
//  HistoricalLiterature
//
//  Created by sol on 2019/6/18.
//  Copyright ? 2019 sol. All rights reserved.
//

#import <Foundation/Foundation.h>
typedef enum {
    kIAPPurchSuccess = 0,       // 購買成功
    kIAPPurchFailed = 1,        // 購買失敗
    kIAPPurchCancle = 2,        // 取消購買
    KIAPPurchVerFailed = 3,     // 訂單校驗失敗
    KIAPPurchVerSuccess = 4,    // 訂單校驗成功
    kIAPPurchNotArrow = 5,      // 不允許內購
}IAPPurchType;

typedef void (^IAPCompletionHandle)(IAPPurchType type,NSData *data);

@interface IAPManager : NSObject

- (void)startPurchWithID:(NSString *)purchID completeHandle:(IAPCompletionHandle)handle;

@end

//
//  IAPManager.m
//  HistoricalLiterature
//
//  Created by sol on 2019/6/18.
//  Copyright ? 2019 sol. All rights reserved.
//

#import "IAPManager.h"
#import <StoreKit/StoreKit.h>    //導入支付包

@interface IAPManager () <SKPaymentTransactionObserver,SKProductsRequestDelegate>
@property (nonatomic,strong) NSString *purchID;
@property (nonnull,strong) IAPCompletionHandle handle;
@end

@implementation IAPManager

#pragma mark - system lifecycle
- (instancetype)init{
    self = [super init];
    if (self) {
        // 購買監聽寫在程序入口,程序掛起時移除監聽,這樣如果有未完成的訂單將會自動執行并回調 paymentQueue:updatedTransactions:方法
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    return self;
}

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

#pragma mark - Public Method
- (void)startPurchWithID:(NSString *)purchID completeHandle:(IAPCompletionHandle)handle{
    if (purchID) {
        if ([SKPaymentQueue canMakePayments]) {
            // 開始購買服務
            self.purchID = purchID;
            self.handle = handle;
            NSSet *nsset = [NSSet setWithArray:@[purchID]];
            SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
            request.delegate = self;
            [request start];
        }else{
            [self handleActionWithType:kIAPPurchNotArrow data:nil];
        }
    }
}

#pragma mark - Private Method
- (void)handleActionWithType:(IAPPurchType)type data:(NSData *)data{
#if DEBUG
    switch (type) {
        case kIAPPurchSuccess:
            NSLog(@"購買成功");
            break;
        case kIAPPurchFailed:
            NSLog(@"購買失敗");
            break;
        case kIAPPurchCancle:
            NSLog(@"用戶取消購買");
            break;
        case KIAPPurchVerFailed:
            NSLog(@"訂單校驗失敗");
            break;
        case KIAPPurchVerSuccess:
            NSLog(@"訂單校驗成功");
            break;
        case kIAPPurchNotArrow:
            NSLog(@"不允許程序內付費");
            break;
        default:
            break;
    }
#endif
    if(self.handle){
        self.handle(type,data);
    }
}
// 交易結束
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
    [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:NO];
}

// 交易失敗
- (void)failedTransaction:(SKPaymentTransaction *)transaction{
    if (transaction.error.code != SKErrorPaymentCancelled) {
        [self handleActionWithType:kIAPPurchFailed data:nil];
    }else{
        [self handleActionWithType:kIAPPurchCancle data:nil];
    }
    
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void)verifyPurchaseWithPaymentTransaction:(SKPaymentTransaction *)transaction isTestServer:(BOOL)flag{
    //交易驗證
    NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];
    
    if(!receipt){
        // 交易憑證為空驗證失敗
        [self handleActionWithType:KIAPPurchVerFailed data:nil];
        return;
    }
    // 購買成功將交易憑證發送給服務端進行再次校驗
    [self handleActionWithType:kIAPPurchSuccess data:receipt];
    
    NSError *error;
    NSDictionary *requestContents = @{
                                      @"receipt-data": [receipt base64EncodedStringWithOptions:0]
                                      };
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                          options:0
                                                            error:&error];
    
    if (!requestData) { // 交易憑證為空驗證失敗
        [self handleActionWithType:KIAPPurchVerFailed data:nil];
        return;
    }
    
    //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
    //In the real environment, use https://buy.itunes.apple.com/verifyReceipt
    
    NSString *serverString = @"https://buy.itunes.apple.com/verifyReceipt";
    if (flag) {
        serverString = @"https://sandbox.itunes.apple.com/verifyReceipt";
    }
    NSURL *storeURL = [NSURL URLWithString:serverString];
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:requestData];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                               if (connectionError) {
                                   // 無法連接服務器,購買校驗失敗
                                   [self handleActionWithType:KIAPPurchVerFailed data:nil];
                               } else {
                                   NSError *error;
                                   NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
                                   if (!jsonResponse) {
                                       // 蘋果服務器校驗數據返回為空校驗失敗
                                       [self handleActionWithType:KIAPPurchVerFailed data:nil];
                                   }
                                   
                                   // 先驗證正式服務器,如果正式服務器返回21007再去蘋果測試服務器驗證,沙盒測試環境蘋果用的是測試服務器
                                   NSString *status = [NSString stringWithFormat:@"%@",jsonResponse[@"status"]];
                                   if (status && [status isEqualToString:@"21007"]) {
                                       [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:YES];
                                   }else if(status && [status isEqualToString:@"0"]){
                                       [self handleActionWithType:KIAPPurchVerSuccess data:nil];
                                   }
#if DEBUG
                                   NSLog(@"----驗證結果 %@",jsonResponse);
#endif
                               }
                           }];
    
    
    // 驗證成功與否都注銷交易,否則會出現虛假憑證信息一直驗證不通過,每次進程序都得輸入蘋果賬號
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    NSArray *product = response.products;
    if([product count] <= 0){
#if DEBUG
        NSLog(@"--------------沒有商品------------------");
#endif
        return;
    }
    
    SKProduct *p = nil;
    for(SKProduct *pro in product){
        if([pro.productIdentifier isEqualToString:self.purchID]){
            p = pro;
            break;
        }
    }
    
#if DEBUG
    NSLog(@"productID:%@", response.invalidProductIdentifiers);
    NSLog(@"產品付費數量:%lu",(unsigned long)[product count]);
    NSLog(@"%@",[p description]);
    NSLog(@"%@",[p localizedTitle]);
    NSLog(@"%@",[p localizedDescription]);
    NSLog(@"%@",[p price]);
    NSLog(@"%@",[p productIdentifier]);
    NSLog(@"發送購買請求");
#endif
    
    SKPayment *payment = [SKPayment paymentWithProduct:p];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

//請求失敗
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
#if DEBUG
    NSLog(@"------------------錯誤-----------------:%@", error);
#endif
}

- (void)requestDidFinish:(SKRequest *)request{
#if DEBUG
    NSLog(@"------------反饋信息結束-----------------");
#endif
}

#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
    for (SKPaymentTransaction *tran in transactions) {
        switch (tran.transactionState) {
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:tran];
                break;
            case SKPaymentTransactionStatePurchasing:
#if DEBUG
                NSLog(@"商品添加進列表");
#endif
                break;
            case SKPaymentTransactionStateRestored:
#if DEBUG
                NSLog(@"已經購買過商品");
#endif
                // 消耗型不支持恢復購買
                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:tran];
                break;
            default:
                break;
        }
    }
}

@end
2.使用方法:
注:初始化方法 宏定義
// 懶加載
#define QH_LAZY(object, assignment) (object = object ?: assignment)
///內購
@property (nonatomic,strong) IAPManager *iapManager;    
///初始化
-(IAPManager *)iapManager{
    return QH_LAZY(_iapManager, ({
        IAPManager *iap = [[IAPManager alloc]init];
        iap;
    }));
}
///使用
//點擊購買按鈕后,先從后臺獲取訂單號(order_id),然后再發起內購.
//plyID 為 產品ID,之前在創建的時候自定義的
[self.iapManager startPurchWithID:plyID completeHandle:^(IAPPurchType type,NSData *data) {
                NSLog(@"data --- %@",data);
                if (type == kIAPPurchSuccess) {      // 購買成功
                    if (data) {       //返回數據
                        [FSHUD showWithStatus:@"購買成功,請稍等"];
                            NSLog(@"http://購買成功");
                            NSString *base64String = [data base64EncodedStringWithOptions:0];
                            //將請求到的數據與傳給服務器  order_id 為訂單ID,從后臺獲取
                            //[self apple_pay:base64String order_id:order_id];  
                    }
                }else if(type == kIAPPurchCancle){
                    [FSHUD showErrorWithStatus:@"已取消購買"];
                }else if(type == kIAPPurchNotArrow){
                    [FSHUD showErrorWithStatus:@"不允許內購"];
                }else if(type == kIAPPurchFailed){
                    [FSHUD showErrorWithStatus:@"購買失敗"];
                }

//                if (type == KIAPPurchVerSuccess) {          //訂單第二次校驗成功
//                    NSLog(@"http://訂單第二次校驗成功");
//                    ///購買憑證
//                    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
//                    NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
//                    NSString *receiptStr = [receipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
//
//                    NSDictionary *dict =[data mj_JSONObject];
//                    NSLog(@"%@",dict);
//                    [FSHUD dismiss];
//                }
            }];

3.開始測試:

點擊購買按鈕彈出登錄頁面
輸入你添加的測試員賬號和密碼來完成登錄


登錄.PNG

蘋果服務器驗證后彈出提示框


50.PNG

點擊購買后,會在之前的代碼中回調type的類型,判斷類型是否為kIAPPurchSuccess(購買成功),然后再做后續的操作
以下為購買成功的提示
內購測試購買商品不扣錢....


購買成功.PNG

五.遇到過的問題

1.在提交審核的時候提示 : 無法存儲您的 App 信息。請再試一次。如果問題仍然存在,請聯系我們。

方法1:刪除最新添加的內購商品重新添加就可以解決了,應該是蘋果的bug
方法2:換個網絡或者重啟路由
方法3:刪除了再添加還是提示上面那個問題的話,可以試試這個提交審核"無法存儲您的App信息"
注: 我試了,一直加載正在提交APP內購項目....
最后的解決方法是刪除掉舊的商品,然后重新添加..

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。