蘋(píng)果內(nèi)購(gòu)小結(jié) - iOS

此篇針對(duì) iOS 支付進(jìn)行一次小結(jié),很久沒(méi)碰這塊了,有些方法 Apple 官方也進(jìn)行了優(yōu)化,故也將隨之進(jìn)行更新.

首先,code 部分將分為兩部分,一部分在 appdelegate 中,另一部分單獨(dú)封裝在了一個(gè)類(lèi)中執(zhí)行,需要使用的地方調(diào)用的接口方法.

其次,大體支付流程為獲取到充值價(jià)格訂單列表后,選擇對(duì)應(yīng)的價(jià)格后向 Apple 發(fā)起支付請(qǐng)求,接收到 Apple 支付回調(diào)后,根據(jù)結(jié)果處理相關(guān)邏輯,最后將處理完成的結(jié)果反饋至用戶(hù).

其過(guò)程中會(huì)分為幾個(gè)環(huán)節(jié)來(lái)處理:

若支付失敗則執(zhí)行異常處理并將最后處理結(jié)果信息反饋至用戶(hù);

若支付成功則對(duì)支付憑證校驗(yàn),此篇文章中的校驗(yàn)過(guò)程分為兩部分,先是由客戶(hù)端自行校驗(yàn),若校驗(yàn)成功則將相關(guān)用戶(hù)信息和支付憑證發(fā)送至服務(wù)端進(jìn)行二次校驗(yàn);

其中,客戶(hù)端優(yōu)先進(jìn)行交易憑證校驗(yàn),校驗(yàn)失敗則將校驗(yàn)的異常處理信息反饋至用戶(hù);反之,校驗(yàn)成功則再次將相關(guān)用戶(hù)信息和本次支付憑證數(shù)據(jù)一并發(fā)送至服務(wù)端進(jìn)行二次校驗(yàn),最終將雙重驗(yàn)證后的結(jié)果信息反饋至用戶(hù),從而為了避免刷單的情況.

最后,文章中具體處理邏輯中可能會(huì)因?yàn)樾枨蟮牟煌c實(shí)際有些小的出入,但大體流程應(yīng)該是一致的,也會(huì)對(duì)應(yīng)添加相應(yīng)的注釋,若存在不清楚的地方可以帖子下方留言溝通交流.

大致支付流程:

1.蘋(píng)果APP(商家)

2.告訴蘋(píng)果Store服務(wù)器要賣(mài)的商品

3.蘋(píng)果審核完(告訴你是否可以賣(mài))

4.用戶(hù)(買(mǎi)商品)

5.蘋(píng)果APP(商家)

6.開(kāi)發(fā)票給(用戶(hù))

7.用戶(hù)(拿著發(fā)票去蘋(píng)果Store服務(wù)器付款)

8.付款成功(用戶(hù)在APP里獲得服務(wù)商品)

注:如果要模擬測(cè)試內(nèi)購(gòu),需要用真機(jī)才可以測(cè)試

憑證校驗(yàn)地址:

開(kāi)發(fā)環(huán)境: https://sandbox.itunes.apple.com/verifyReceipt

生產(chǎn)環(huán)境: https://buy.itunes.apple.com/verifyReceipt

憑證校驗(yàn)異常 code 參照碼:

內(nèi)購(gòu)驗(yàn)證憑據(jù)返回結(jié)果狀態(tài)碼說(shuō)明(status 狀態(tài))

         21000 App Store無(wú)法讀取你提供的JSON數(shù)據(jù)

         21002 收據(jù)數(shù)據(jù)不符合格式

         21003 收據(jù)無(wú)法被驗(yàn)證             

         21004 你提供的共享密鑰和賬戶(hù)的共享密鑰不一致             

         21005 收據(jù)服務(wù)器當(dāng)前不可用

         21006 收據(jù)是有效的伴奥,但訂閱服務(wù)已經(jīng)過(guò)期藐唠。當(dāng)收到這個(gè)信息時(shí)笛坦,解碼后的收據(jù)信息也包含在返回內(nèi)容中

         21007 收據(jù)信息是測(cè)試用(sandbox),但卻被發(fā)送到產(chǎn)品環(huán)境中驗(yàn)證

         21008 收據(jù)信息是產(chǎn)品環(huán)境中使用廓推,但卻被發(fā)送到測(cè)試環(huán)境中驗(yàn)證
蘋(píng)果內(nèi)購(gòu)如何正確處理 transaction 交易事物.jpg

Code 如下:

/*
 支付_管理類(lèi)(Apple Pay)
 */
 
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
 
//充值金額類(lèi)型
typedef NS_ENUM(NSInteger, buyCoinsTag) {
    IAP0p20=20,
    IAP1p100,
    IAP4p600,
    IAP9p1000,
    IAP24p6000,
};
 
@import WebKit;
 
@interface PaymentManager : NSObject <SKPaymentTransactionObserver, SKProductsRequestDelegate> {
    /** 購(gòu)買(mǎi)類(lèi)型*/
    int buyType;
}
 
/** 產(chǎn)品 ID*/
@property (nonatomic, strong) NSString *productID;
 
/** Init*/
+ (PaymentManager *)manager;
 
#pragma mark - 方法相關(guān)
/** 判斷當(dāng)前環(huán)境是否支持支付購(gòu)買(mǎi)*/
- (void)buyInfo:(NSDictionary *)buyDic AndViewController:(UIViewController *)vc AndSn:(NSString *)sn;
 
/**
 校驗(yàn)交易憑證 - App Store (Plan A)
 
 該方法以客戶(hù)端為基準(zhǔn):
 若客戶(hù)端校驗(yàn)結(jié)果失敗,則服務(wù)器不再進(jìn)行二次校驗(yàn);
 若客戶(hù)端校驗(yàn)結(jié)果成功,則服務(wù)器再次進(jìn)行二次校驗(yàn).
 
 @param transaction 交易事物
 */
- (void)verifyTransaction:(SKPaymentTransaction *)transaction;
 
/** 交易失敗*/
- (void)failedTransaction:(SKPaymentTransaction *)transaction;
 
 
 
@end
/*
 1.蘋(píng)果APP(商家)
 2.告訴蘋(píng)果Store服務(wù)器要賣(mài)的商品
 3.蘋(píng)果審核完(告訴你是否可以賣(mài))
 4.用戶(hù)(買(mǎi)商品)
 5.蘋(píng)果APP(商家)
 6.開(kāi)發(fā)票給(用戶(hù))
 7.用戶(hù)(拿著發(fā)票去蘋(píng)果Store服務(wù)器付款)
 8.付款成功(用戶(hù)在APP里獲得服務(wù)商品)
 
 注:如果要模擬測(cè)試內(nèi)購(gòu),需要用真機(jī)才可以測(cè)試
 */
 
#import "PaymentManager.h"
#import <objc/runtime.h>
 
//在內(nèi)購(gòu)項(xiàng)目中創(chuàng)的商品單號(hào)
#define ProductID_IAP0p20       @"***此處與實(shí)際內(nèi)購(gòu)價(jià)格配置表為準(zhǔn)***" //20
#define ProductID_IAP1p100      @"***此處與實(shí)際內(nèi)購(gòu)價(jià)格配置表為準(zhǔn)***" //100
#define ProductID_IAP4p600      @"***此處與實(shí)際內(nèi)購(gòu)價(jià)格配置表為準(zhǔn)***" //600
#define ProductID_IAP9p1000     @"***此處與實(shí)際內(nèi)購(gòu)價(jià)格配置表為準(zhǔn)***" //1000
#define ProductID_IAP24p6000    @"***此處與實(shí)際內(nèi)購(gòu)價(jià)格配置表為準(zhǔn)***" //6000
 
#define PaySucceed      @"充值成功"
#define PayFailed       @"充值失敗"
#define PayException    @"訂單發(fā)生異常,請(qǐng)聯(lián)系客服"
#define RequestError    @"支付成功,等待驗(yàn)證"
 
/*
 AppStore增加了驗(yàn)證內(nèi)購(gòu)(In App Purchasement)的方法, 就是蘋(píng)果提供一個(gè)url地址:
 
 當(dāng)購(gòu)買(mǎi)成功時(shí), 會(huì)得到蘋(píng)果返回的一個(gè)收據(jù)(receipt), 蘋(píng)果推薦的方法是將收據(jù)發(fā)給開(kāi)發(fā)者的 server 服務(wù)端, 由 server 服務(wù)端向上述地址發(fā)起請(qǐng)求(post http)消息, 進(jìn)行驗(yàn)證, 蘋(píng)果將校驗(yàn)結(jié)果返回.此次交易憑證是真購(gòu)買(mǎi)憑證還是偽購(gòu)買(mǎi)憑證.
 
 開(kāi)發(fā)環(huán)境地址:  https://sandbox.itunes.apple.com/verifyReceipt
 生成環(huán)境地址:  https://buy.itunes.apple.com/verifyReceipt
 */
 
#define SANDBOX_VERIFY_RECEIPT_URL          [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"]
#define APP_STORE_VERIFY_RECEIPT_URL        [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"]
 
#ifdef DEBUG
#define VERIFY_RECEIPT_URL SANDBOX_VERIFY_RECEIPT_URL
#else
#define VERIFY_RECEIPT_URL APP_STORE_VERIFY_RECEIPT_URL
#endif
 
 
@interface PaymentManager () <SKPaymentTransactionObserver,SKProductsRequestDelegate, SKRequestDelegate>
 
/** 訂單編號(hào)*/
@property (nonatomic, strong) NSString *tradeNo;
 
@end
 
@implementation PaymentManager
 
#pragma mark - Init
+ (PaymentManager *)manager {
    static id sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
 
#pragma mark - ************************************ 支付初始化
/**
 Pay Method 支付初始化
 具體方法介紹詳見(jiàn)上面注釋中連接地址
 注:切記綁定 <SKPaymentTransactionObserver> 事件,設(shè)置購(gòu)買(mǎi)隊(duì)列的監(jiān)聽(tīng)器,實(shí)時(shí)監(jiān)聽(tīng)跟蹤訂單狀態(tài),避免發(fā)送丟單的意外,即 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 
 
 @param buyDic  支付所對(duì)應(yīng)的產(chǎn)品信息
 @param vc      當(dāng)前 VC 控件
 @param sn      用戶(hù)信息
 */
- (void)buyInfo:(NSDictionary *)buyDic AndViewController:(UIViewController *)vc AndSn:(NSString *)sn {
    //判斷網(wǎng)絡(luò)是否可用
    self.productID = [NSString stringWithFormat:@"%@", [buyDic objectForKey:@"productId"]];
    // 訂單編號(hào)
    NSString *tradeNo = [NSString stringWithFormat:@"%@", [result objectForKey:@"tradeNo"]];
            
    //設(shè)置購(gòu)買(mǎi)隊(duì)列的監(jiān)聽(tīng)器
    [[SKPaymentQueue defaultQueue] addTransactionObserver:weakself];
            
    //判斷當(dāng)前是否可支付
    if ([SKPaymentQueue canMakePayments]) {
          NSLog(@"允許程序內(nèi)付費(fèi)購(gòu)買(mǎi)");
                
           //請(qǐng)求產(chǎn)品數(shù)據(jù)
          [self fetchProductInformationForIds:self.productID AndTradeNo:tradeNo];
                
    } else {
          NSLog(@"不允許程序內(nèi)付費(fèi)購(gòu)買(mǎi)");
 
          // Callback
          //[MBProgressHUD showError:@"請(qǐng)開(kāi)啟手機(jī)內(nèi)付費(fèi)購(gòu)買(mǎi)功能" toView:vc.view];
          [self hudAlertMessage:@"請(qǐng)開(kāi)啟手機(jī)內(nèi)付費(fèi)購(gòu)買(mǎi)功能"];
    }
    
}
 
/**
 獲取對(duì)應(yīng)的產(chǎn)品數(shù)據(jù)信息
 @param productIds  產(chǎn)品 id
 @param tradeNo     訂單編號(hào)
 */
- (void)fetchProductInformationForIds:(NSString *)productIds AndTradeNo:(NSString *)tradeNo {
    NSLog(@"------------請(qǐng)求對(duì)應(yīng)的產(chǎn)品信息------------");
    self.tradeNo = tradeNo;
    NSArray *product = [[NSArray alloc] initWithObjects:productIds, nil];
    //為該產(chǎn)品標(biāo)識(shí)符創(chuàng)建一個(gè)集合
    NSSet *nsSet = [NSSet setWithArray:product];
    //創(chuàng)建該產(chǎn)品請(qǐng)求對(duì)象,并將上面的集合進(jìn)行初始化它
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsSet];
    request.delegate = self;
    //向 App Store 發(fā)起請(qǐng)求
    [request start];
}
 
 
 
#pragma mark - ************************************ 交易處理中
/**
 獲取 App Store 產(chǎn)品反饋信息
 注:設(shè)置請(qǐng)求協(xié)議代理:    <SKProductsRequestDelegate>
 
 @param request     請(qǐng)求
 @param response    應(yīng)用結(jié)果
 */
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSLog(@"------------收到產(chǎn)品反饋消息------------");
    /** 當(dāng)前產(chǎn)品信息*/
    NSArray *myProduct = response.products;
    if (0 == myProduct.count) {
        NSLog(@"------------暫無(wú)商品------------");
        // Callback
        //[MBProgressHUD showError:@"App Store支付異常,請(qǐng)重新嘗試" toView:vc.view];
        [self hudAlertMessage:@"App Store支付異常,請(qǐng)重新嘗試"];
        
        return;
        
    }
    else {
        NSLog(@"------------預(yù)購(gòu)商品------------");
        
        SKProduct *p = nil;
        for (SKProduct *product in myProduct) {
            NSLog(@"*** 產(chǎn)品信息相關(guān)[product info] ***\n1.描述信息(SKProduct): %@\n2.產(chǎn)品標(biāo)題(Title): %@\n3.產(chǎn)品描述信息(Description): %@\n4.產(chǎn)品價(jià)格(Price): %@\n5.產(chǎn)品 id(Product id): %@", product, product.localizedTitle, product.localizedDescription, product.price, product.productIdentifier);
 
            if([product.productIdentifier isEqualToString:self.productID]){
                p = product;
            }
        }
        
        if (p != nil) {
            // 將要購(gòu)買(mǎi)的產(chǎn)品
            SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:p];// The product is available, let's submit a payment request to the queue
            NSLog(@"------------發(fā)送購(gòu)買(mǎi)請(qǐng)求------------");
            // 發(fā)起準(zhǔn)備購(gòu)買(mǎi)流程(異步)
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
    }
}
 
 
 
#pragma mark - ************************************ 交易校驗(yàn)中
/**
 校驗(yàn)交易憑證 - App Store
 該方法以客戶(hù)端為基準(zhǔn):
 若客戶(hù)端校驗(yàn)結(jié)果失敗,則服務(wù)器不再進(jìn)行二次校驗(yàn);
 若客戶(hù)端校驗(yàn)結(jié)果成功,則服務(wù)器再次進(jìn)行二次校驗(yàn).
 
 @param transaction 交易事物
 */
- (void)verifyTransaction:(SKPaymentTransaction *)transaction {
    /*
        使用如下方法獲取購(gòu)買(mǎi)憑證也 ok,則需要對(duì) data 進(jìn)行判空操作,
        NSData *transactionReceipt = [PaymentManager receiptDataFromTransaction:transaction];
        // 若 data 為空則執(zhí)行如下方法
        SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
        receiptRefreshRequest.delegate = self;
        [receiptRefreshRequest start];
        return;
    */
    // 從沙盒中獲取到購(gòu)買(mǎi)憑據(jù)
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
        NSData *transactionReceipt = [NSData dataWithContentsOfURL:receiptURL];
        NSString *encodeStr = [transactionReceipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//轉(zhuǎn)化為base64字符串
        /*
         驗(yàn)證自動(dòng)訂閱的有效 receipt 示例
         {
             "receipt-data"  : "...",
             "password"      : "..."
         }
         */
        // 拼接請(qǐng)求數(shù)據(jù) 
        NSString *bodyStr = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
        NSData *bodyData = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
        // 創(chuàng)建請(qǐng)求,驗(yàn)證憑證,蘋(píng)果服務(wù)器比較坑,建議超時(shí)時(shí)長(zhǎng)設(shè)置稍稍長(zhǎng)一些
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:VERIFY_RECEIPT_URL
                                                               cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                           timeoutInterval:10.0f];
        request.HTTPBody = bodyData;
        request.HTTPMethod = @"POST";
        
        NSError *error = nil;
        // 創(chuàng)建連接并發(fā)送同步請(qǐng)求,獲得官方的驗(yàn)證JSON結(jié)果
        NSData *responseData = [NSURLConnection sendSynchronousRequest:request
                                                     returningResponse:nil
                                                                 error:&error];
        if (error) {
            NSLog(@"App Store 驗(yàn)證購(gòu)買(mǎi)過(guò)程中發(fā)生錯(cuò)誤,錯(cuò)誤信息: %@",error.localizedDescription);
            
            // Callback
            //[MBProgressHUD showError:@"網(wǎng)絡(luò)請(qǐng)求超時(shí),請(qǐng)重試" toView:vc.view];
            [self hudAlertMessage:@"網(wǎng)絡(luò)請(qǐng)求超時(shí),請(qǐng)重試"];
            
        }
        else {
            NSDictionary *result = [NSJSONSerialization JSONObjectWithData:responseData
                                                                   options:NSJSONReadingAllowFragments
                                                                     error:&error];
            
            /*
             內(nèi)購(gòu)驗(yàn)證憑據(jù)返回結(jié)果狀態(tài)碼說(shuō)明(status 狀態(tài))
             
             21000 App Store無(wú)法讀取你提供的JSON數(shù)據(jù)
             
             21002 收據(jù)數(shù)據(jù)不符合格式
             
             21003 收據(jù)無(wú)法被驗(yàn)證
             
             21004 你提供的共享密鑰和賬戶(hù)的共享密鑰不一致
             
             21005 收據(jù)服務(wù)器當(dāng)前不可用
             
             21006 收據(jù)是有效的,但訂閱服務(wù)已經(jīng)過(guò)期。當(dāng)收到這個(gè)信息時(shí)艇炎,解碼后的收據(jù)信息也包含在返回內(nèi)容中
             
             21007 收據(jù)信息是測(cè)試用(sandbox),但卻被發(fā)送到產(chǎn)品環(huán)境中驗(yàn)證
             
             21008 收據(jù)信息是產(chǎn)品環(huán)境中使用腾窝,但卻被發(fā)送到測(cè)試環(huán)境中驗(yàn)證
             */
            NSString *status = [NSString stringWithFormat:@"%@", [result objectForKey:@"status"]];
            if (0 == [status intValue]) {
                NSLog(@"App Store 驗(yàn)證購(gòu)買(mǎi) --- 成功");
                NSDictionary *dicReceipt = [NSDictionary dictionaryWithDictionary:[result objectForKey:@"receipt"]];
                NSArray *arrInApp = [dicReceipt objectForKey:@"in_app"];
                // 注:此處 in_app 字段中數(shù)據(jù)可能為多個(gè),需進(jìn)行循環(huán)
                NSMutableDictionary *dicInAppReult = [NSMutableDictionary dictionary];
                for (NSDictionary *dict in arrInApp) {
                    /** 產(chǎn)品標(biāo)識(shí)*/
                    NSString *product_id = [NSString stringWithFormat:@"%@", [dict objectForKey:@"product_id"]];
                    /** 事物標(biāo)識(shí)*/
                    NSString *transaction_id = [NSString stringWithFormat:@"%@", [dict objectForKey:@"transaction_id"]];
                    [dicInAppReult setValue:product_id forKey:transaction_id];
                }
 
                NSString *bundle_id = [NSString stringWithFormat:@"%@", [dicReceipt objectForKey:@"bundle_id"]];
                NSString *application_version = [NSString stringWithFormat:@"%@", [dicReceipt objectForKey:@"application_version"]];
                if ([bundle_id isEqualToString:kGetBundleId] &&
                    [application_version isEqualToString:kAppBundle] &&
                    [dicInAppReult.allKeys containsObject:transaction.transactionIdentifier] &&
                    [[dicInAppReult objectForKey:transaction.transactionIdentifier] isEqualToString:transaction.payment.productIdentifier]
                    ) {
                    NSLog(@"App Store 憑證驗(yàn)證 --- 成功");
                    
                    // 交易成功且憑證驗(yàn)證成功向服務(wù)端提交憑證進(jìn)行處理
                    [self commitSeversSucceeWithTransaction:transaction];
                    
                }
                else {
                    NSLog(@"App Store 憑證驗(yàn)證 --- 失敗");
                    
                    // Remove the transaction from the payment queue.
                    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                    
                    // Callback
                    //[MBProgressHUD showError:PayFailed toView:vc.view];
                    [self hudAlertMessage:PayFailed];
                }
                
            }
            else {
                NSLog(@"App Store 憑證驗(yàn)證 --- 失敗: %@", error.localizedDescription);
                
                // Remove the transaction from the payment queue.
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                
                // Callback
                //[MBProgressHUD showError:PayFailed toView:vc.view];
                [self hudAlertMessage:PayFailed];
            }
        }
        
    }
    else {
        SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
        receiptRefreshRequest.delegate = self;
        [receiptRefreshRequest start];
        
        return;
    }
}
 
/**
 獲取交易憑證
 @param transaction 交易事物
 @return 結(jié)果集
 */
+ (NSData *)receiptDataFromTransaction:(SKPaymentTransaction *)transaction {
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
        // 從沙盒中獲取到購(gòu)買(mǎi)憑據(jù)
        NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
        if (!receiptData) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
            if ([transaction respondsToSelector:@selector(transactionReceipt)]) {
                //Works in iOS3 - iOS8, deprected since iOS7, actual deprecated (returns nil) since
                receiptData = transaction.transactionReceipt;
            }
#pragma clang diagnostic pop
        }
        return receiptData;
        
    }
    else {
        return nil;
    }
}
 
 
 
#pragma mark - ************************************ 交易成功 - 向公司服務(wù)器驗(yàn)證購(gòu)買(mǎi)憑證
/**
 交易成功 - 向公司服務(wù)器驗(yàn)證購(gòu)買(mǎi)憑證
 status:0:訂單開(kāi)始,1:充值成功,2:充值失敗
 
 @param transaction 交易事務(wù)
 */
- (void)commitSeversSucceeWithTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"------------交易成功向公司服務(wù)器驗(yàn)證購(gòu)買(mǎi)憑證------------");
    
#pragma mark - 交易憑證相關(guān)
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    if (![[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
        // 取 receipt 的時(shí)候要判空,如果文件不存在,就要從蘋(píng)果服務(wù)器重新刷新下載 receipt 了
        // SKReceiptRefreshRequest 刷新的時(shí)候,需要用戶(hù)輸入 Apple ID,同時(shí)需要網(wǎng)絡(luò)狀態(tài)良好
        SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
        receiptRefreshRequest.delegate = self;
        [receiptRefreshRequest start];
        return;
    }
    NSData *data = [NSData dataWithContentsOfURL:receiptURL];
    /** 交易憑證*/
    NSString *receipt_data = [data base64EncodedStringWithOptions:0];
    /** 事務(wù)標(biāo)識(shí)符(交易編號(hào))  交易編號(hào)(必傳:防止越獄下內(nèi)購(gòu)被破解,校驗(yàn) in_app 參數(shù))*/
    NSString *transaction_id = transaction.transactionIdentifier;
    
    // 此處忽略,純好奇心所驅(qū),一個(gè)神奇的 data,拆不出來(lái) ... 你贏了
//    NSString * test1 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//    NSString * test2 = [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
//    NSError *error;
//    NSDictionary * test3 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
    
    // 判空相關(guān)
    if (receipt_data == nil) {
        receipt_data = @"";
    }
    
    if (transaction_id == nil) {
        transaction_id = @"";
    }
    
    NSLog(@"交易憑證:\n%@", receipt_data);
    NSLog(@"事務(wù)標(biāo)識(shí)符(交易編號(hào)):\n%@",  transaction_id);
    NSLog(@"產(chǎn)品標(biāo)識(shí)符(內(nèi)購(gòu)產(chǎn)品編號(hào)) --- productIdentifier:\n%@", transaction.payment.productIdentifier);
//    NSLog(@"交易日期\nDate: %@,Date(String): %@", transaction_date, strTransaction_date);
//    NSLog(@"事物狀態(tài):\n%@", transaction_state);
    
    
    
    NSMutableDictionary *dicParameter = [NSMutableDictionary dictionary];
    [dicParameter setValue:kAppBundle forKey:@"appBundle"];
    [dicParameter setValue:transaction_id forKey:@"transactionId"];// 查明交易標(biāo)識(shí)符(防止越獄下內(nèi)購(gòu)被破解,校驗(yàn) in_app 參數(shù))
    [dicParameter setValue:receipt_data forKey:@"receiptData"];// 收到的收據(jù),即收據(jù)證明 transactionReceipt
    kWeakSelf(self);
#pragma mark - 向服務(wù)端發(fā)起請(qǐng)求傳遞所需憑證參數(shù)
    // Request
    [[HttpRequestManager shareInstance] PayPOST:URL_ApplePay parameters:dicParameter isEncipherment:NO success:^(id responseObject) {
        NSDictionary *result = [NSDictionary dictionaryWithDictionary:responseObject];
        NSString *status = [NSString stringWithFormat:@"%@", [result objectForKey:@"payStatus"]];
        NSLog(@"Pay Callback Result: %@", result);
        // Callback
        if ([status isEqualToString:@"success"]) {// 成功
            // Callback
            //[MBProgressHUD showError:PaySucceed toView:vc.view];
            [self hudAlertMessage:PaySucceed];
        }
        else {
            // Callback
            //[MBProgressHUD showError:PayException toView:vc.view];
            [self hudAlertMessage:PayException];
        }
        
        // Remove the transaction from the payment queue.
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        
    } failure:^(NSError *error) {
        NSLog(@"commitSeversSucceeWithTransaction: %@", error.localizedDescription);
        
        // Callback
        //[MBProgressHUD showError:RequestError toView:vc.view];
        [self hudAlertMessage:RequestError];
    }];
}
 
 
 
#pragma mark - ************************************ 交易失敗相關(guān)
/*
 
 內(nèi)購(gòu)驗(yàn)證憑據(jù)返回結(jié)果狀態(tài)碼說(shuō)明
 
 21000 App Store無(wú)法讀取你提供的JSON數(shù)據(jù)
 
 21002 收據(jù)數(shù)據(jù)不符合格式
 
 21003 收據(jù)無(wú)法被驗(yàn)證
 
 21004 你提供的共享密鑰和賬戶(hù)的共享密鑰不一致
 
 21005 收據(jù)服務(wù)器當(dāng)前不可用
 
 21006 收據(jù)是有效的缀踪,但訂閱服務(wù)已經(jīng)過(guò)期。當(dāng)收到這個(gè)信息時(shí)虹脯,解碼后的收據(jù)信息也包含在返回內(nèi)容中
 
 21007 收據(jù)信息是測(cè)試用(sandbox)驴娃,但卻被發(fā)送到產(chǎn)品環(huán)境中驗(yàn)證
 
 21008 收據(jù)信息是產(chǎn)品環(huán)境中使用,但卻被發(fā)送到測(cè)試環(huán)境中驗(yàn)證
 
 */
#pragma mark - 交易失敗 -> 彈出錯(cuò)誤信息 Error
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"*** 交易失敗 Error code: (%d)",transaction.error.code);
    if (transaction.error != nil) {
        switch (transaction.error.code) {
                
            case SKErrorUnknown:
                NSLog(@"SKErrorUnknown --- 未知的錯(cuò)誤循集,您可能正在使用越獄手機(jī)");
                break;
                
            case SKErrorClientInvalid:
                NSLog(@"SKErrorClientInvalid --- 當(dāng)前蘋(píng)果賬戶(hù)無(wú)法購(gòu)買(mǎi)商品(如有疑問(wèn)唇敞,可以詢(xún)問(wèn)蘋(píng)果客服)");
                break;
                
            case SKErrorPaymentCancelled:
                NSLog(@"SKErrorPaymentCancelled --- 訂單已取消");
                break;
                
            case SKErrorPaymentInvalid:
                NSLog(@"SKErrorPaymentInvalid --- 訂單無(wú)效(如有疑問(wèn),可以詢(xún)問(wèn)蘋(píng)果客服)");
                break;
                
            case SKErrorPaymentNotAllowed:
                NSLog(@"SKErrorPaymentNotAllowed --- 當(dāng)前蘋(píng)果設(shè)備無(wú)法購(gòu)買(mǎi)商品(如有疑問(wèn)咒彤,可以詢(xún)問(wèn)蘋(píng)果客服)");
                break;
                
            case SKErrorStoreProductNotAvailable:
                NSLog(@"SKErrorStoreProductNotAvailable --- 當(dāng)前商品不可用");
                break;
                
            default:
                
                NSLog(@"No Match Found for error -- 未知錯(cuò)誤");
                break;
        }
    }
    
    // Callback
    //[MBProgressHUD showError:PayFailed toView:vc.view];
    [self hudAlertMessage:PayFailed];
 
    // Remove the transaction from the payment queue.
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
 
-(void)requestDidFinish:(SKRequest *)request {
    NSLog(@"------------反饋信息結(jié)束------------");
}
 
#pragma mark - 交易失敗 - 請(qǐng)求失敗 -> 彈出錯(cuò)誤信息 Error
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
    NSLog(@"------------彈出錯(cuò)誤信息------------");
    
    // Callback
    //[MBProgressHUD showError:error.localizedDescription toView:vc.view];
    [self hudAlertMessage:error.localizedDescription];
}
 
 
 
#pragma mark - 交易恢復(fù)處理
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
    // Remove the transaction from the payment queue.
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
 
#pragma mark - 完成付款隊(duì)列恢復(fù)完成交易
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentTransaction *)transaction {
    NSLog(@"****** 完成付款隊(duì)列恢復(fù)完成交易: %@", transaction.transactionIdentifier);
 
    /*
     NSMutableArray *productIDsToRestore =  From the user ;
     SKPaymentTransaction *transaction =  Current transaction ;
     if ([productIDsToRestore containsObject:transaction.transactionIdentifier]) {
        // Re-download the Apple-hosted content, then finish the transaction
        // and remove the product identifier from the array of product IDs.
     } else {
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
     }
     */
 
}
 
#pragma mark - 付款隊(duì)列
- (void)paymentQueue:(SKPaymentQueue *)paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error {
    NSLog(@"------------付款隊(duì)列(當(dāng)從用戶(hù)的購(gòu)買(mǎi)歷史記錄向隊(duì)列添加事務(wù)時(shí)遇到錯(cuò)誤時(shí)發(fā)送)------------\n%@", error.localizedDescription);
}
 
#pragma mark - 購(gòu)買(mǎi)交易
- (void)purchasedTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"------------購(gòu)買(mǎi)交易------------");
    NSArray *transactions =[[NSArray alloc] initWithObjects:transaction, nil];
    [self paymentQueue:[SKPaymentQueue defaultQueue] updatedTransactions:transactions];
}
 
 
 
/**
 提示框
 
 @param msg 提示語(yǔ)
 */
- (void)hudAlertMessage:(NSString *)msg {
    UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@""
                                                        message:msg
                                                       delegate:nil
                                              cancelButtonTitle:NSLocalizedString(@"確定",nil)
                                              otherButtonTitles:nil];
    [alerView show];
}
 
 
 
#pragma mark - ************************************ Connection delegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    NSLog(@"connection delegate --- %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    
}
 
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    switch([(NSHTTPURLResponse *)response statusCode]) {
        case 200:
        case 206:
            break;
        case 304:
            break;
        case 400:
            break;
        case 404:
            break;
        case 416:
            break;
        case 403:
            break;
        case 401:
        case 500:
            break;
        default:
            break;
    }
}
 
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"connection:(NSURLConnection *)connection didFailWithError:(NSError *)error: %@", error.localizedDescription);
}
 
- (void)dealloc {
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];// 解除監(jiān)聽(tīng)
}

AppDelegate 中 Code:

#import "AppDelegate.h"
// Apple pay
#import <StoreKit/StoreKit.h>
#import "PaymentManager.h"
 
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    // 將觀察者添加到支付隊(duì)列中
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    
    return YES;
}
 
 
 
#pragma mark - ****************************** Apple Pay
/**
 監(jiān)聽(tīng)購(gòu)買(mǎi)交易結(jié)果 transactions
 
 @param queue           交易隊(duì)列
 @param transactions    交易事物
 */
- (void)paymentQueue:(nonnull SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {
    
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:// 商品添加進(jìn)列表
                // Transaction is being added to the server queue.
                NSLog(@"------------商品添加進(jìn)列表------------");
                /* 不用finish疆柔,繼續(xù)觀察支付隊(duì)列,等待transaction狀態(tài)改變 */
                
                break;
            case SKPaymentTransactionStateDeferred:
                // The transaction is in the queue, but its final status is pending external action.
                NSLog(@"------------事務(wù)在隊(duì)列中,但其最終狀態(tài)是等待外部操作");
                /* 不用finish镶柱,繼續(xù)觀察支付隊(duì)列 */
                
                break;
            case SKPaymentTransactionStateFailed:// 交易失敗
                // Transaction was cancelled or failed before being added to the server queue.
                NSLog(@"------------交易失敗: %@", transaction.error.localizedDescription);
                /* 檢查錯(cuò)誤并根據(jù)需要處理旷档,然后調(diào)用 finishTransaction */
                
                [[PaymentManager manager] failedTransaction:transaction];
                
                break;
            case SKPaymentTransactionStatePurchased:// 交易完成
                // Transaction is in queue, user has been charged.  Client should complete the transaction.
                NSLog(@"------------交易完成: %@", transaction.payment.productIdentifier);
                /* 分發(fā)內(nèi)容給用戶(hù),然后調(diào)用 finishTransaction */
                [[PaymentManager manager] verifyTransaction:transaction];
                
                break;
            case SKPaymentTransactionStateRestored:// 已經(jīng)購(gòu)買(mǎi)過(guò)該商品
                // Transaction was restored from user's purchase history.  Client should complete the transaction.
                NSLog(@"------------已經(jīng)購(gòu)買(mǎi)過(guò)該商品");
                /* 分發(fā)內(nèi)容給用戶(hù)歇拆,然后調(diào)用 finishTransaction */
                [[PaymentManager manager] verifyTransaction:transaction];
                
                break;
                
            default:
                break;
        }
    }
}

以上便是此次內(nèi)購(gòu)支付相關(guān)小結(jié),還望多多指點(diǎn)交流!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鞋屈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子查吊,更是在濱河造成了極大的恐慌谐区,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逻卖,死亡現(xiàn)場(chǎng)離奇詭異宋列,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)评也,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)炼杖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人盗迟,你說(shuō)我怎么就攤上這事坤邪。” “怎么了罚缕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵艇纺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)黔衡,這世上最難降的妖魔是什么蚓聘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮盟劫,結(jié)果婚禮上夜牡,老公的妹妹穿的比我還像新娘。我一直安慰自己侣签,他們只是感情好塘装,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著影所,像睡著了一般蹦肴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上型檀,一...
    開(kāi)封第一講書(shū)人閱讀 52,682評(píng)論 1 312
  • 那天冗尤,我揣著相機(jī)與錄音,去河邊找鬼胀溺。 笑死裂七,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仓坞。 我是一名探鬼主播背零,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼无埃!你這毒婦竟也來(lái)了徙瓶?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嫉称,失蹤者是張志新(化名)和其女友劉穎侦镇,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體织阅,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡壳繁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荔棉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闹炉。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖润樱,靈堂內(nèi)的尸體忽然破棺而出渣触,到底是詐尸還是另有隱情,我是刑警寧澤壹若,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布嗅钻,位于F島的核電站皂冰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏啊犬。R本人自食惡果不足惜灼擂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一壁查、第九天 我趴在偏房一處隱蔽的房頂上張望觉至。 院中可真熱鬧,春花似錦睡腿、人聲如沸语御。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)应闯。三九已至,卻和暖如春挂捻,著一層夾襖步出監(jiān)牢的瞬間碉纺,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工刻撒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骨田,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓声怔,卻偏偏與公主長(zhǎng)得像态贤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子醋火,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 1悠汽、通過(guò)CocoaPods安裝項(xiàng)目名稱(chēng)項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明先生_X自主閱讀 15,988評(píng)論 3 119
  • 多年后那個(gè)撿垃圾的老人的畫(huà)面一直會(huì)浮現(xiàn)在我的腦海中。 那是高三的時(shí)候芥驳,在燥熱的夏天中知了在急切地叫著柿冲,太陽(yáng)烤著大地...
    淡水無(wú)魚(yú)閱讀 915評(píng)論 0 0
  • 從婺源回深圳已經(jīng)有一個(gè)禮拜了,今天才整理完相片兆旬,記錄一下…… “最美鄉(xiāng)村在婺源假抄,山川秀麗甲東南【粼鳎” 初...
    旖旎的風(fēng)光閱讀 287評(píng)論 0 1