iOS開發 AIP支付總結

iOS開發 IAP支付總結

一、IAP介紹

1.1、簡介

這里先把官方文檔給大家

App 內購買項目配置流程

內購:只要在iPhone App上購買的不是實物產品(也就是虛擬產品如qq幣、魚翅、電子書......) 都需要走內購流程,蘋果這里面抽走30%

蘋果規定,凡是在App內提供的服務需要付費時,必須使用IAP,比如說游戲的金幣,道具等;而在App外提供的服務需要付費時,可以使用其他的支付方式,比如支付寶SDK、微信SDK等。說的更通俗一點,如果付費購買的商品是虛擬商品,比如游戲中的道具,并不是現實中存在的,那么必須使用IAP;如果付費購買的商品是真實產品,比如在淘寶中買了件衣服,是實實在在存在的,那么沒有必要使用IAP。因此,在使用IAP之前,首先要確認是否一定要使用IAP,如果不使用IAP也可以,那么盡量不要用IAP,因為IAP流程、使用復雜度相比支付寶SDK、微信SDK來說,要復雜很多。

1.2、內購流程

1.1.1 填寫協議,稅務和銀行業務

1、登錄https://appstoreconnect.apple.com,選擇進入App Store Connect。

2、進入“協議、稅務和銀行業務”

3、內購用的是付費應用程序,先簽署《付費應用程序協議》,同意后狀態變更為“用戶信息待處理”,等待審核。

4、狀態更改完畢后,點擊“開始設置稅務、銀行業務和聯系信息”。
a.添加銀行賬戶,按照要求填寫相關內容即可。


b.選擇報稅表,并填寫。(我是可愛的中國公民,在美國有沒有商業活動,所以我填的是否。)

然后繼續填寫報稅表,按照填寫要求填寫就行了(要是英文閱讀有點困難,那就雙擊網頁,應該會有翻譯成中文的功能;沒有的話,那就詞典。。。你懂得,哈哈哈), 我是個人開發者賬戶相對公司開發者賬戶填的會少一點,不過沒關系。都是一些基本信息。

c.填寫聯系信息,一共5個。高級管理、財務、技術、法務、營銷。

5、上面的稅務表填完了之后,點擊“我的APP”,進入到項目APP的信息頁,點擊功能,在彈出的頁面點擊App內購買項目后面的+。

創建完成之后 填寫內購買項目信息

信息填寫完成了點擊右上角的 “存儲”,然后點擊左邊 “App 內購買項目”。出現“元數據丟失”說明里面信息沒填寫完整,在點進去填寫。直到顯示“準備提交”。

6、添加沙箱測試人員

7、我們需要在工程里配置好證書,測試證書是必須的因為我們內購需要連接到蘋果的App Store的,需要正式的測試證書才能測試,同時把下圖工程中的這一配置打開

二、IAP代碼部分

我這里就直接上代碼記錄了

2.1、大體代碼流程

typedefenum: NSUInteger {

    EPaymentTransactionStateNoPaymentPermission,//沒有Payment權限

    EPaymentTransactionStateAddPaymentFailed,//addPayment失敗

    EPaymentTransactionStatePurchasing,//正在購買

    EPaymentTransactionStatePurchased,//購買完成(銷毀交易)

    EPaymentTransactionStateFailed,//購買失敗(銷毀交易)

    EPaymentTransactionStateCancel,//用戶取消

    EPaymentTransactionStateRestored,//恢復購買(銷毀交易)

    EPaymentTransactionStateDeferred,//最終狀態未確定

} EPaymentTransactionState;

// 這個大家要熟悉哦~

步驟一:App Store請求內購項

注意:此步驟建議在開始創建購買訂單前完成,這樣可以減少購買時查詢訂單的時間

1、判斷用戶是否具備支付權限

//是否允許內購
if ([SKPaymentQueue canMakePayments]) {
    [self getRequestAppleProduct];
}else{
    [self removeLoadingHandle];
    [self removeIAPObserverHandle];
    dispatch_async(dispatch_get_main_queue(), ^{
        [MBProgressHUDManager showFailedHUD:TZKeyWindow text:@"請打開Apple支付"];
    });
}

2、創建一個商品查詢的請求,productIdentifiers指需要查詢的“產品ID”的數組

- (void)getRequestAppleProduct
{
    NSLog(@"---------請求對應的產品信息------------");
    [MBProgressHUDManager showHUD:TZKeyWindow text:@"等待響應..."];
    NSArray *product = [[NSArray alloc] initWithObjects:self.productID, nil];
    NSSet *nsset = [NSSet setWithArray:product];
    
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
    request.delegate = self;
    
    [request start];
    
}

查詢的結果將通過SKProductsRequestDelegate得到查詢的結果
獲取商品的查詢結果

#pragma mark - SKProductsRequestDelegate
//接收到產品的返回信息,然后用返回的商品信息進行發起購買請求
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response NS_AVAILABLE_IOS(3_0){
    
    
    NSArray *product = response.products;
    //沒有產品
    if([product count] == 0){
        [self removeLoadingHandle];
        [self removeIAPObserverHandle];
        dispatch_async(dispatch_get_main_queue(), ^{
            [MBProgressHUDManager showFailedHUD:TZKeyWindow text:@"網絡開小差了,請稍后重試"];
        });
        return;
    }
    
    
    SKProduct *requestProduct = nil;
    for (SKProduct *pro in product) {
        
        CYLOG(@"描述信息-%@", [pro description]);
        CYLOG(@"產品標題-%@", [pro localizedTitle]);
        CYLOG(@"產品描述信息-%@", [pro localizedDescription]);
        CYLOG(@"價格-%@", [pro price]);
        CYLOG(@"Product id-%@", [pro productIdentifier]);
        CYLOG(@"位置-%@", pro.priceLocale.localeIdentifier);
        
        
        // 確保訂單的正確性
        if([pro.productIdentifier isEqualToString:self.productID]){
            requestProduct = pro;
            SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestProduct];
            payment.applicationUsername = self.orderId;
            [[SKPaymentQueue defaultQueue] addPayment:payment];
            break;
        }
    }
}``

步驟二:開始構建購買請求

SKPayment * payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];

步驟三:添加支付交易的Observer

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

注意在適當的時候移除Observer

[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

可以通過遵循SKPaymentTransactionObserver協議來監聽整個交易的過程

交易狀態發生改變時,包括狀態的改變,交易的結束

//監聽購買結果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    
    NSLog(@"==監聽購買結果==");
    
    [self addLoadingHandle];
    [self addIAPObserverHandle];
    
    for(SKPaymentTransaction *tran in transactions){
        switch (tran.transactionState) {
            case SKPaymentTransactionStatePurchased:
            {
                NSLog(@"交易完成");
                [self didPurchaseTransaction:tran queue:queue];
            }
                
                break;
            case SKPaymentTransactionStatePurchasing:{
                dispatch_async(dispatch_get_main_queue(), ^{
                    [MBProgressHUDManager showHUD:TZKeyWindow text:@"正在購買..."];
                });
            }
                
                break;
            case SKPaymentTransactionStateRestored:{
                CYLOG(@"已經購買過商品");
                //消耗型不用寫
//                                [self removeLoadingHandle];
//                                [self removeIAPObserverHandle];
//                                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
            }
                break;
            case SKPaymentTransactionStateFailed:{
                NSLog(@"交易失敗");
                [self removeLoadingHandle];
                [self removeIAPObserverHandle];
                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [MBProgressHUDManager showFailedHUD:TZKeyWindow text:@"交易失敗"];
                });
            }
                
                break;
            case SKPaymentTransactionStateDeferred:{
                NSLog(@"還在隊列里");
                dispatch_async(dispatch_get_main_queue(), ^{
                    [MBProgressHUDManager showHUD:TZKeyWindow text:@"正在購買..."];
                });
            }
                
                break;
            default:
                break;
        }
    }
}

步驟四:校驗憑證

我這里直接把我這邊相關的思路以及代碼提供大家參考了:
有問題可以隨時聯系我、我后面會講一下我所遇到過的坑以及解決方案。

#pragma mark Transaction State

我這里使用后臺校驗憑證、更加安全
- (void)didPurchaseTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue
{
    CYLOG(@"transaction purchased with product ---%@", transaction.payment.productIdentifier);
    CYLOG(@"transaction ID ---%@", transaction.transactionIdentifier);
    if(transaction.payment.productIdentifier != nil){
        if([self.orderId length] && !self.ischecking){
            //如果這個參數存在,則肯定是通過主動發起購買請求引起的
            //在支付成功后,將parameters中的預訂單號存起來,并與蘋果的訂單號綁定起來,并存儲到keychain中
            if([self.orderId length] && transaction.transactionIdentifier){
                [SAMKeychain setPassword:self.orderId forService:TZServiceKey account:transaction.transactionIdentifier];
            }
        }
    }
    WS(weakSelf);
    // appStoreReceiptURL iOS7.0增加的,購買交易完成后,會將憑據存放在該地址
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    // 從沙盒中獲取到購買憑據
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
    
    NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
    NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
    NSString *applicationUsername = transaction.payment.applicationUsername;
    NSString *productId = transaction.payment.productIdentifier;
    NSString *transactionId = transaction.transactionIdentifier;
    //發送POST請求,對購買憑據進行驗證
    //測試驗證地址:https://sandbox.itunes.apple.com/verifyReceipt
    //正式驗證地址:https://buy.itunes.apple.com/verifyReceipt
    if(applicationUsername.length == 0){
        NSString *savedOrderNumber = [SAMKeychain passwordForService:TZServiceKey account:transactionId];
        if ([savedOrderNumber length]) {
            applicationUsername = savedOrderNumber;//獲取到訂單號
        }
    }
    if ([applicationUsername length] && [encodeStr length] && [productId length] && [transactionId length]) {
        [[HTTPAPIManager manager] reqeustPayAppleReceiptWithOutTradeNo:applicationUsername receiptData:encodeStr useSandbox:TZPAYSandbox productId:productId transactionId:transactionId success:^(NSURLSessionDataTask * _Nullable task, id  _Nullable responseObject, NSDictionary * _Nullable inforDict) {
            _errTimes = 0;
            weakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:(2.0)
                                                              target:weakSelf
                                                            selector:@selector(handleTimer:)
                                                            userInfo:@{@"transaction":transaction}
                                                             repeats:YES];
            [[NSRunLoop mainRunLoop]addTimer:weakSelf.timer forMode:NSDefaultRunLoopMode];
            [weakSelf.timer setFireDate:[NSDate date]];
        } failure:^(NSURLSessionDataTask * _Nullable task, YWHTTPError * _Nullable error, NSDictionary * _Nullable responseDict) {
            _errTimes = 0;
            weakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:(2.0)
                                                              target:weakSelf
                                                            selector:@selector(handleTimer:)
                                                            userInfo:@{@"transaction":transaction}
                                                             repeats:YES];
            [[NSRunLoop mainRunLoop]addTimer:weakSelf.timer forMode:NSDefaultRunLoopMode];
            [weakSelf.timer setFireDate:[NSDate date]];
        }];
        if (!_ischecking) {
            [MBProgressHUDManager showHUD:TZKeyWindow text:@"正在確認支付..."];
        }
    } else {
        [[HTTPAPIManager manager] reqeustAppleFailRecordWithOutTradeNo:applicationUsername receiptData:encodeStr productId:productId transactionId:transactionId success:^(NSURLSessionDataTask * _Nullable task, id  _Nullable responseObject, NSDictionary * _Nullable inforDict) {
            _ischecking = NO;
            [MBProgressHUDManager hiddenHUD:TZKeyWindow];
            [weakSelf destroyTimer];
            [weakSelf removeLoadingHandle];
            [weakSelf removeIAPObserverHandle];
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            [[NSNotificationCenter defaultCenter] postNotificationName:TZBuyVipSuccessNotification object:nil userInfo:nil];
        } failure:^(NSURLSessionDataTask * _Nullable task, YWHTTPError * _Nullable error, NSDictionary * _Nullable responseDict) {
            [MBProgressHUDManager hiddenHUD:TZKeyWindow];
        }];
    }
}

三、重點總結

1.獲取內購列表(從App內讀取或從自己服務器讀取)

2.App Store請求可用的內購列表

3.向用戶展示內購列表

4.用戶選擇了內購列表,再發個購買請求,收到購買完成的回調(購買完成
后會把錢打給申請內購的銀行卡內)

5.購買流程結束后, 向服務器發起驗證憑證以及支付結果的請求

6.自己的服務器將支付結果信息返回給前端并發放虛擬產品

7.服務端的工作比較簡單,分4步:

  7.1.接收ios端發過來的購買憑證。
  
  7.2.判斷憑證是否已經存在或驗證過,然后存儲該憑證。
  
  7.3.將該憑證發送到蘋果的服務器驗證,并將驗證結果返回給客戶端。
  
   7.4.如果需要,修改用戶相應的會員權限。
   
   7.5.考慮到網絡異常情況,服務器的驗證應該是一個可恢復的隊列,如果網絡失敗了,應該進行重試。
   
簡單來說就是將該購買憑證用Base64編碼,然后POST給蘋果的驗證服務
器,蘋果將驗證結果以JSON形式返回。

四、總結坑

SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestProduct];
payment.applicationUsername = self.orderId;
[[SKPaymentQueue defaultQueue] addPayment:payment];
//我這里剛才只是對訂單號的存儲、并且把訂單號存在了applicationUsername 

上面的處理中、上線后我這里第一個測出了真實支付中的漏單情況、訂單號返回的nil,由于對payment.applicationUsername的極度信任、造成自己不得不緊急解決下這個問題發版、不過我事先作的還是有一些功課的、做了埋點、及時的定位到了問題、并且讓后臺進行了手動補單。相信大家看到這里的時候就不會只是簡單的這樣做了。

五、解決方案

1、針對掉單的問題、網上的資料討論的太多了我這里簡單說下我的方案吧

我這里進行了對訂單號、transactionId、進行對應的鑰匙串存儲。這樣可以解決大部分的異常場景、基本沒什么漏單了、并且我對掉單每次啟動進行了掉單查詢、還有就是再次購買頁面也會有相應的提示、讓用戶自己繼續處理、便可以重新提交訂單了。方便太多啦

/// 掉單處理
- (void)checkIAPHandle{
    _ischecking = YES;
    _isShowErrorM = NO;
    [self addIAPObserverHandle];
}


- (void)checkTransactionHandle
{
    NSArray *transactions = [SKPaymentQueue defaultQueue].transactions;
    if (transactions && [transactions isKindOfClass:[NSArray class]] && [transactions count]) {
        for (SKPaymentTransaction *transaction in transactions) {
            if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
                 NSString *title = @"您有一筆會員訂單未完成,請繼續處理";
                           NYAlertView *alertView = [[NYAlertView alloc] initWithTitle:@"發現未完成訂單"
                                                                               message:title
                                                                     cancelButtonTitle:nil
                                                                     otherButtonTitles:@"繼續處理", nil];
                           [alertView setClickButtonBlock:^(NYAlertView * _Nonnull alert, NSInteger index) {
                               [[ApplePayManager sharedManager] handleCheckPurchaseTransaction:transaction];
                           }];
                           alertView.titleTextAlignment = NSTextAlignmentLeft;
                           alertView.messageTextAlignment = NSTextAlignmentLeft;
                           [alertView.otherButton setTitleColor:[UIColor wb_colorWithHexString:@"F44A4A"] forState:UIControlStateNormal];
                           [alertView show];
            }
        }
    }
}

六、附言

大家做內購過程中遇到問題可以隨時溝通哈!!! QQ:304517331

有好的建議也記得及時分享哦??

祝大家工作順利!!!!~~~~

七、干貨

https://developer.apple.com/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store?language=objc

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