苹果内购小结 - iOS

此篇针对 iOS 支付进行一次小结,很久没碰这块了,有些方法 Apple 官方也进行了优化,故也将随之进行更新.

首先,code 部分将分为两部分,一部分在 appdelegate 中,另一部分单独封装在了一个类中执行,需要使用的地方调用的接口方法.

其次,大体支付流程为获取到充值价格订单列表后,选择对应的价格后向 Apple 发起支付请求,接收到 Apple 支付回调后,根据结果处理相关逻辑,最后将处理完成的结果反馈至用户.

其过程中会分为几个环节来处理:

若支付失败则执行异常处理并将最后处理结果信息反馈至用户;

若支付成功则对支付凭证校验,此篇文章中的校验过程分为两部分,先是由客户端自行校验,若校验成功则将相关用户信息和支付凭证发送至服务端进行二次校验;

其中,客户端优先进行交易凭证校验,校验失败则将校验的异常处理信息反馈至用户;反之,校验成功则再次将相关用户信息和本次支付凭证数据一并发送至服务端进行二次校验,最终将双重验证后的结果信息反馈至用户,从而为了避免刷单的情况.

最后,文章中具体处理逻辑中可能会因为需求的不同与实际有些小的出入,但大体流程应该是一致的,也会对应添加相应的注释,若存在不清楚的地方可以帖子下方留言沟通交流.

大致支付流程:

1.苹果APP(商家)

2.告诉苹果Store服务器要卖的商品

3.苹果审核完(告诉你是否可以卖)

4.用户(买商品)

5.苹果APP(商家)

6.开发票给(用户)

7.用户(拿着发票去苹果Store服务器付款)

8.付款成功(用户在APP里获得服务商品)

注:如果要模拟测试内购,需要用真机才可以测试

凭证校验地址:

开发环境: https://sandbox.itunes.apple.com/verifyReceipt

生产环境: https://buy.itunes.apple.com/verifyReceipt

凭证校验异常 code 参照码:

内购验证凭据返回结果状态码说明(status 状态)

         21000 App Store无法读取你提供的JSON数据

         21002 收据数据不符合格式

         21003 收据无法被验证             

         21004 你提供的共享密钥和账户的共享密钥不一致             

         21005 收据服务器当前不可用

         21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中

         21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证

         21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
苹果内购如何正确处理 transaction 交易事物.jpg

Code 如下:

/*
 支付_管理类(Apple Pay)
 */
 
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
 
//充值金额类型
typedef NS_ENUM(NSInteger, buyCoinsTag) {
    IAP0p20=20,
    IAP1p100,
    IAP4p600,
    IAP9p1000,
    IAP24p6000,
};
 
@import WebKit;
 
@interface PaymentManager : NSObject <SKPaymentTransactionObserver, SKProductsRequestDelegate> {
    /** 购买类型*/
    int buyType;
}
 
/** 产品 ID*/
@property (nonatomic, strong) NSString *productID;
 
/** Init*/
+ (PaymentManager *)manager;
 
#pragma mark - 方法相关
/** 判断当前环境是否支持支付购买*/
- (void)buyInfo:(NSDictionary *)buyDic AndViewController:(UIViewController *)vc AndSn:(NSString *)sn;
 
/**
 校验交易凭证 - App Store (Plan A)
 
 该方法以客户端为基准:
 若客户端校验结果失败,则服务器不再进行二次校验;
 若客户端校验结果成功,则服务器再次进行二次校验.
 
 @param transaction 交易事物
 */
- (void)verifyTransaction:(SKPaymentTransaction *)transaction;
 
/** 交易失败*/
- (void)failedTransaction:(SKPaymentTransaction *)transaction;
 
 
 
@end
/*
 1.苹果APP(商家)
 2.告诉苹果Store服务器要卖的商品
 3.苹果审核完(告诉你是否可以卖)
 4.用户(买商品)
 5.苹果APP(商家)
 6.开发票给(用户)
 7.用户(拿着发票去苹果Store服务器付款)
 8.付款成功(用户在APP里获得服务商品)
 
 注:如果要模拟测试内购,需要用真机才可以测试
 */
 
#import "PaymentManager.h"
#import <objc/runtime.h>
 
//在内购项目中创的商品单号
#define ProductID_IAP0p20       @"***此处与实际内购价格配置表为准***" //20
#define ProductID_IAP1p100      @"***此处与实际内购价格配置表为准***" //100
#define ProductID_IAP4p600      @"***此处与实际内购价格配置表为准***" //600
#define ProductID_IAP9p1000     @"***此处与实际内购价格配置表为准***" //1000
#define ProductID_IAP24p6000    @"***此处与实际内购价格配置表为准***" //6000
 
#define PaySucceed      @"充值成功"
#define PayFailed       @"充值失败"
#define PayException    @"订单发生异常,请联系客服"
#define RequestError    @"支付成功,等待验证"
 
/*
 AppStore增加了验证内购(In App Purchasement)的方法, 就是苹果提供一个url地址:
 
 当购买成功时, 会得到苹果返回的一个收据(receipt), 苹果推荐的方法是将收据发给开发者的 server 服务端, 由 server 服务端向上述地址发起请求(post http)消息, 进行验证, 苹果将校验结果返回.此次交易凭证是真购买凭证还是伪购买凭证.
 
 开发环境地址:  https://sandbox.itunes.apple.com/verifyReceipt
 生成环境地址:  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>
 
/** 订单编号*/
@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 支付初始化
 具体方法介绍详见上面注释中连接地址
 注:切记绑定 <SKPaymentTransactionObserver> 事件,设置购买队列的监听器,实时监听跟踪订单状态,避免发送丢单的意外,即 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 
 
 @param buyDic  支付所对应的产品信息
 @param vc      当前 VC 控件
 @param sn      用户信息
 */
- (void)buyInfo:(NSDictionary *)buyDic AndViewController:(UIViewController *)vc AndSn:(NSString *)sn {
    //判断网络是否可用
    self.productID = [NSString stringWithFormat:@"%@", [buyDic objectForKey:@"productId"]];
    // 订单编号
    NSString *tradeNo = [NSString stringWithFormat:@"%@", [result objectForKey:@"tradeNo"]];
            
    //设置购买队列的监听器
    [[SKPaymentQueue defaultQueue] addTransactionObserver:weakself];
            
    //判断当前是否可支付
    if ([SKPaymentQueue canMakePayments]) {
          NSLog(@"允许程序内付费购买");
                
           //请求产品数据
          [self fetchProductInformationForIds:self.productID AndTradeNo:tradeNo];
                
    } else {
          NSLog(@"不允许程序内付费购买");
 
          // Callback
          //[MBProgressHUD showError:@"请开启手机内付费购买功能" toView:vc.view];
          [self hudAlertMessage:@"请开启手机内付费购买功能"];
    }
    
}
 
/**
 获取对应的产品数据信息
 @param productIds  产品 id
 @param tradeNo     订单编号
 */
- (void)fetchProductInformationForIds:(NSString *)productIds AndTradeNo:(NSString *)tradeNo {
    NSLog(@"------------请求对应的产品信息------------");
    self.tradeNo = tradeNo;
    NSArray *product = [[NSArray alloc] initWithObjects:productIds, nil];
    //为该产品标识符创建一个集合
    NSSet *nsSet = [NSSet setWithArray:product];
    //创建该产品请求对象,并将上面的集合进行初始化它
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsSet];
    request.delegate = self;
    //向 App Store 发起请求
    [request start];
}
 
 
 
#pragma mark - ************************************ 交易处理中
/**
 获取 App Store 产品反馈信息
 注:设置请求协议代理:    <SKProductsRequestDelegate>
 
 @param request     请求
 @param response    应用结果
 */
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSLog(@"------------收到产品反馈消息------------");
    /** 当前产品信息*/
    NSArray *myProduct = response.products;
    if (0 == myProduct.count) {
        NSLog(@"------------暂无商品------------");
        // Callback
        //[MBProgressHUD showError:@"App Store支付异常,请重新尝试" toView:vc.view];
        [self hudAlertMessage:@"App Store支付异常,请重新尝试"];
        
        return;
        
    }
    else {
        NSLog(@"------------预购商品------------");
        
        SKProduct *p = nil;
        for (SKProduct *product in myProduct) {
            NSLog(@"*** 产品信息相关[product info] ***\n1.描述信息(SKProduct): %@\n2.产品标题(Title): %@\n3.产品描述信息(Description): %@\n4.产品价格(Price): %@\n5.产品 id(Product id): %@", product, product.localizedTitle, product.localizedDescription, product.price, product.productIdentifier);
 
            if([product.productIdentifier isEqualToString:self.productID]){
                p = product;
            }
        }
        
        if (p != nil) {
            // 将要购买的产品
            SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:p];// The product is available, let's submit a payment request to the queue
            NSLog(@"------------发送购买请求------------");
            // 发起准备购买流程(异步)
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
    }
}
 
 
 
#pragma mark - ************************************ 交易校验中
/**
 校验交易凭证 - App Store
 该方法以客户端为基准:
 若客户端校验结果失败,则服务器不再进行二次校验;
 若客户端校验结果成功,则服务器再次进行二次校验.
 
 @param transaction 交易事物
 */
- (void)verifyTransaction:(SKPaymentTransaction *)transaction {
    /*
        使用如下方法获取购买凭证也 ok,则需要对 data 进行判空操作,
        NSData *transactionReceipt = [PaymentManager receiptDataFromTransaction:transaction];
        // 若 data 为空则执行如下方法
        SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
        receiptRefreshRequest.delegate = self;
        [receiptRefreshRequest start];
        return;
    */
    // 从沙盒中获取到购买凭据
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
        NSData *transactionReceipt = [NSData dataWithContentsOfURL:receiptURL];
        NSString *encodeStr = [transactionReceipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串
        /*
         验证自动订阅的有效 receipt 示例
         {
             "receipt-data"  : "...",
             "password"      : "..."
         }
         */
        // 拼接请求数据 
        NSString *bodyStr = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
        NSData *bodyData = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
        // 创建请求,验证凭证,苹果服务器比较坑,建议超时时长设置稍稍长一些
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:VERIFY_RECEIPT_URL
                                                               cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                           timeoutInterval:10.0f];
        request.HTTPBody = bodyData;
        request.HTTPMethod = @"POST";
        
        NSError *error = nil;
        // 创建连接并发送同步请求,获得官方的验证JSON结果
        NSData *responseData = [NSURLConnection sendSynchronousRequest:request
                                                     returningResponse:nil
                                                                 error:&error];
        if (error) {
            NSLog(@"App Store 验证购买过程中发生错误,错误信息: %@",error.localizedDescription);
            
            // Callback
            //[MBProgressHUD showError:@"网络请求超时,请重试" toView:vc.view];
            [self hudAlertMessage:@"网络请求超时,请重试"];
            
        }
        else {
            NSDictionary *result = [NSJSONSerialization JSONObjectWithData:responseData
                                                                   options:NSJSONReadingAllowFragments
                                                                     error:&error];
            
            /*
             内购验证凭据返回结果状态码说明(status 状态)
             
             21000 App Store无法读取你提供的JSON数据
             
             21002 收据数据不符合格式
             
             21003 收据无法被验证
             
             21004 你提供的共享密钥和账户的共享密钥不一致
             
             21005 收据服务器当前不可用
             
             21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
             
             21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
             
             21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
             */
            NSString *status = [NSString stringWithFormat:@"%@", [result objectForKey:@"status"]];
            if (0 == [status intValue]) {
                NSLog(@"App Store 验证购买 --- 成功");
                NSDictionary *dicReceipt = [NSDictionary dictionaryWithDictionary:[result objectForKey:@"receipt"]];
                NSArray *arrInApp = [dicReceipt objectForKey:@"in_app"];
                // 注:此处 in_app 字段中数据可能为多个,需进行循环
                NSMutableDictionary *dicInAppReult = [NSMutableDictionary dictionary];
                for (NSDictionary *dict in arrInApp) {
                    /** 产品标识*/
                    NSString *product_id = [NSString stringWithFormat:@"%@", [dict objectForKey:@"product_id"]];
                    /** 事物标识*/
                    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 凭证验证 --- 成功");
                    
                    // 交易成功且凭证验证成功向服务端提交凭证进行处理
                    [self commitSeversSucceeWithTransaction:transaction];
                    
                }
                else {
                    NSLog(@"App Store 凭证验证 --- 失败");
                    
                    // 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 凭证验证 --- 失败: %@", 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 结果集
 */
+ (NSData *)receiptDataFromTransaction:(SKPaymentTransaction *)transaction {
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
        // 从沙盒中获取到购买凭据
        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 - ************************************ 交易成功 - 向公司服务器验证购买凭证
/**
 交易成功 - 向公司服务器验证购买凭证
 status:0:订单开始,1:充值成功,2:充值失败
 
 @param transaction 交易事务
 */
- (void)commitSeversSucceeWithTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"------------交易成功向公司服务器验证购买凭证------------");
    
#pragma mark - 交易凭证相关
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    if (![[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
        // 取 receipt 的时候要判空,如果文件不存在,就要从苹果服务器重新刷新下载 receipt 了
        // SKReceiptRefreshRequest 刷新的时候,需要用户输入 Apple ID,同时需要网络状态良好
        SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
        receiptRefreshRequest.delegate = self;
        [receiptRefreshRequest start];
        return;
    }
    NSData *data = [NSData dataWithContentsOfURL:receiptURL];
    /** 交易凭证*/
    NSString *receipt_data = [data base64EncodedStringWithOptions:0];
    /** 事务标识符(交易编号)  交易编号(必传:防止越狱下内购被破解,校验 in_app 参数)*/
    NSString *transaction_id = transaction.transactionIdentifier;
    
    // 此处忽略,纯好奇心所驱,一个神奇的 data,拆不出来 ... 你赢了
//    NSString * test1 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//    NSString * test2 = [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
//    NSError *error;
//    NSDictionary * test3 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
    
    // 判空相关
    if (receipt_data == nil) {
        receipt_data = @"";
    }
    
    if (transaction_id == nil) {
        transaction_id = @"";
    }
    
    NSLog(@"交易凭证:\n%@", receipt_data);
    NSLog(@"事务标识符(交易编号):\n%@",  transaction_id);
    NSLog(@"产品标识符(内购产品编号) --- productIdentifier:\n%@", transaction.payment.productIdentifier);
//    NSLog(@"交易日期\nDate: %@,Date(String): %@", transaction_date, strTransaction_date);
//    NSLog(@"事物状态:\n%@", transaction_state);
    
    
    
    NSMutableDictionary *dicParameter = [NSMutableDictionary dictionary];
    [dicParameter setValue:kAppBundle forKey:@"appBundle"];
    [dicParameter setValue:transaction_id forKey:@"transactionId"];// 查明交易标识符(防止越狱下内购被破解,校验 in_app 参数)
    [dicParameter setValue:receipt_data forKey:@"receiptData"];// 收到的收据,即收据证明 transactionReceipt
    kWeakSelf(self);
#pragma mark - 向服务端发起请求传递所需凭证参数
    // 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 - ************************************ 交易失败相关
/*
 
 内购验证凭据返回结果状态码说明
 
 21000 App Store无法读取你提供的JSON数据
 
 21002 收据数据不符合格式
 
 21003 收据无法被验证
 
 21004 你提供的共享密钥和账户的共享密钥不一致
 
 21005 收据服务器当前不可用
 
 21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
 
 21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
 
 21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
 
 */
#pragma mark - 交易失败 -> 弹出错误信息 Error
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"*** 交易失败 Error code: (%d)",transaction.error.code);
    if (transaction.error != nil) {
        switch (transaction.error.code) {
                
            case SKErrorUnknown:
                NSLog(@"SKErrorUnknown --- 未知的错误,您可能正在使用越狱手机");
                break;
                
            case SKErrorClientInvalid:
                NSLog(@"SKErrorClientInvalid --- 当前苹果账户无法购买商品(如有疑问,可以询问苹果客服)");
                break;
                
            case SKErrorPaymentCancelled:
                NSLog(@"SKErrorPaymentCancelled --- 订单已取消");
                break;
                
            case SKErrorPaymentInvalid:
                NSLog(@"SKErrorPaymentInvalid --- 订单无效(如有疑问,可以询问苹果客服)");
                break;
                
            case SKErrorPaymentNotAllowed:
                NSLog(@"SKErrorPaymentNotAllowed --- 当前苹果设备无法购买商品(如有疑问,可以询问苹果客服)");
                break;
                
            case SKErrorStoreProductNotAvailable:
                NSLog(@"SKErrorStoreProductNotAvailable --- 当前商品不可用");
                break;
                
            default:
                
                NSLog(@"No Match Found for error -- 未知错误");
                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(@"------------反馈信息结束------------");
}
 
#pragma mark - 交易失败 - 请求失败 -> 弹出错误信息 Error
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
    NSLog(@"------------弹出错误信息------------");
    
    // Callback
    //[MBProgressHUD showError:error.localizedDescription toView:vc.view];
    [self hudAlertMessage:error.localizedDescription];
}
 
 
 
#pragma mark - 交易恢复处理
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
    // Remove the transaction from the payment queue.
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
 
#pragma mark - 完成付款队列恢复完成交易
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentTransaction *)transaction {
    NSLog(@"****** 完成付款队列恢复完成交易: %@", 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 - 付款队列
- (void)paymentQueue:(SKPaymentQueue *)paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error {
    NSLog(@"------------付款队列(当从用户的购买历史记录向队列添加事务时遇到错误时发送)------------\n%@", error.localizedDescription);
}
 
#pragma mark - 购买交易
- (void)purchasedTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"------------购买交易------------");
    NSArray *transactions =[[NSArray alloc] initWithObjects:transaction, nil];
    [self paymentQueue:[SKPaymentQueue defaultQueue] updatedTransactions:transactions];
}
 
 
 
/**
 提示框
 
 @param msg 提示语
 */
- (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];// 解除监听
}

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.
    
    // 将观察者添加到支付队列中
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    
    return YES;
}
 
 
 
#pragma mark - ****************************** Apple Pay
/**
 监听购买交易结果 transactions
 
 @param queue           交易队列
 @param transactions    交易事物
 */
- (void)paymentQueue:(nonnull SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {
    
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:// 商品添加进列表
                // Transaction is being added to the server queue.
                NSLog(@"------------商品添加进列表------------");
                /* 不用finish,继续观察支付队列,等待transaction状态改变 */
                
                break;
            case SKPaymentTransactionStateDeferred:
                // The transaction is in the queue, but its final status is pending external action.
                NSLog(@"------------事务在队列中,但其最终状态是等待外部操作");
                /* 不用finish,继续观察支付队列 */
                
                break;
            case SKPaymentTransactionStateFailed:// 交易失败
                // Transaction was cancelled or failed before being added to the server queue.
                NSLog(@"------------交易失败: %@", transaction.error.localizedDescription);
                /* 检查错误并根据需要处理,然后调用 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);
                /* 分发内容给用户,然后调用 finishTransaction */
                [[PaymentManager manager] verifyTransaction:transaction];
                
                break;
            case SKPaymentTransactionStateRestored:// 已经购买过该商品
                // Transaction was restored from user's purchase history.  Client should complete the transaction.
                NSLog(@"------------已经购买过该商品");
                /* 分发内容给用户,然后调用 finishTransaction */
                [[PaymentManager manager] verifyTransaction:transaction];
                
                break;
                
            default:
                break;
        }
    }
}

以上便是此次内购支付相关小结,还望多多指点交流!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_x阅读 15,967评论 3 119
  • 多年后那个捡垃圾的老人的画面一直会浮现在我的脑海中。 那是高三的时候,在燥热的夏天中知了在急切地叫着,太阳烤着大地...
    淡水无鱼阅读 900评论 0 0
  • 从婺源回深圳已经有一个礼拜了,今天才整理完相片,记录一下…… “最美乡村在婺源,山川秀丽甲东南。” 初...
    旖旎的风光阅读 278评论 0 1