美文网首页
iOS集成苹果内购

iOS集成苹果内购

作者: 最强的小强 | 来源:发表于2020-03-02 08:30 被阅读0次
    一、创建苹果内购支付管理工具 SIAPManager
    // SIAPManager.h
    #import <Foundation/Foundation.h>
    #import <StoreKit/StoreKit.h>
    
    //沙盒验证地址
    //#define checkURL @"https://sandbox.itunes.apple.com/verifyReceipt"
    
    //正式验证地址
    #define checkURL @"https://buy.itunes.apple.com/verifyReceipt"
    
    // 套装id: com.bnqc.mlt
    // apple id: 1481163429
    // sku: mlt
    // 12个月 产品id mlt_book_year
    
    #pragma mark - SIAPManagerDelegate内购代理
    
    /**
     *  内购工具的代理
     */
    @protocol SIAPManagerDelegate <NSObject>
    
    /**
     *  代理:系统错误
     */
    - (void)IAPToolSysWrong;
    
    /**
     *  代理:购买成功
     *  @param productID 购买成功的商品ID
     */
    - (void)IAPToolBoughtProductSuccessedWithProductID:(NSString *)productID
                                               andInfo:(NSDictionary *)infoDic;
    
    /**
     *  代理:取消购买
     *  @param productID 商品ID
     */
    - (void)IAPToolCanceldWithProductID:(NSString *)productID;
    
    /**
     *  代理:购买成功,开始验证购买
     *  @param productID 商品ID
     */
    - (void)IAPToolBeginCheckingdWithProductID:(NSString *)productID;
    
    /**
     *  代理:验证失败
     *  @param productID 商品ID
     */
    - (void)IAPToolCheckFailedWithProductID:(NSString *)productID
                                    andInfo:(NSData *)infoData;
    
    /**
     *  代理:重复验证
     *  @param productID 商品ID
     */
    - (void)IAPToolCheckRedundantWithProductID:(NSString *)productID;
    
    /**
     *  代理:已刷新可购买商品
     *  @param products 商品数组
     */
    - (void)IAPToolGotProducts:(NSMutableArray *)products;
    
    /**
     *  恢复了已购买的商品(永久性商品)
     *  @param productID 商品ID
     */
    - (void)IAPToolRestoredProductID:(NSString *)productID;
    
    @end
    
    #pragma mark - SIAPManager内购工具
    
    /**
     *  内购工具
     */
    @interface SIAPManager : NSObject
    
    typedef void(^BoolBlock)(BOOL successed, BOOL result);
    typedef void(^DicBlock)(BOOL successed, NSDictionary *result);
    
    /**
     *  代理
     */
    @property(nonatomic,weak)id<SIAPManagerDelegate> delegate;
    
    /**
     *  购买完后是否在iOS端向服务器验证一次,默认为YES
     */
    @property(nonatomic)BOOL CheckAfterPay;
    
    /**
     *  单例
     *  @return SIAPManager
     */
    + (SIAPManager *)defaultTool;
    
    /**
     *  询问苹果的服务器能够销售哪些商品
     *  @param products 商品ID的数组
     */
    - (void)requestProductsWithProductArray:(NSArray *)products;
    
    /**
     *  用户决定购买商品
     *  @param productID 商品ID
     */
    - (void)buyProduct:(NSString *)productID;
    
    /**
     *  恢复商品(仅限永久有效商品)
     */
    - (void)restorePurchase;
    
    @end
    
    // SIAPManager.m
    #import "SIAPManager.h"
    
    @interface SIAPManager ()<SKPaymentTransactionObserver, SKProductsRequestDelegate>
    
    // 商品字典
    @property(nonatomic,strong)NSMutableDictionary *productDict;
    
    @end
    
    @implementation SIAPManager
    
    //单例
    static SIAPManager *storeTool;
    //单例
    + (SIAPManager *)defaultTool {
        if (!storeTool){
            storeTool = [SIAPManager new];
            [storeTool setup];
        }
        return storeTool;
    }
    
    #pragma mark  初始化
    
    - (void)setup {
        self.CheckAfterPay = YES;
        // 设置购买队列的监听器
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    
    #pragma mark 询问苹果的服务器能够销售哪些商品
    
    - (void)requestProductsWithProductArray:(NSArray *)products {
        NSLog(@"开始请求可销售商品");
        // 能够销售的商品
        NSSet *set = [[NSSet alloc] initWithArray:products];
        // "异步"询问苹果能否销售
        SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
        request.delegate = self;
        // 启动请求
        [request start];
    }
    
    #pragma mark 获取询问结果,成功采取操作把商品加入可售商品字典里
    
    /**
     *  获取询问结果,成功采取操作把商品加入可售商品字典里
     *  @param request  请求内容
     *  @param response 返回的结果
     */
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
        if (self.productDict == nil) {
            self.productDict = [NSMutableDictionary dictionaryWithCapacity:response.products.count];
        }
        NSMutableArray *productArray = [NSMutableArray array];
        for (SKProduct *product in response.products) {
            //NSLog(@"%@", product.productIdentifier);
            // 填充商品字典
            [self.productDict setObject:product forKey:product.productIdentifier];
            [productArray addObject:product];
        }
        //通知代理
        [self.delegate IAPToolGotProducts:productArray];
    }
    
    #pragma mark - 用户决定购买商品
    
    - (void)buyProduct:(NSString *)productID {
        SKProduct *product = self.productDict[productID];
        // 要购买产品(店员给用户开了个小票)
        SKPayment *payment = [SKPayment paymentWithProduct:product];
        // 去收银台排队,准备购买(异步网络)   
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
    
    #pragma mark - SKPaymentTransaction Observer
    #pragma mark 购买队列状态变化,,判断购买状态是否成功
    
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
        for (SKPaymentTransaction *transaction in transactions) {
            NSLog(@"队列状态变化 %@", transaction);
            if (SKPaymentTransactionStatePurchased == transaction.transactionState) { // 状态是购买完成
                NSLog(@"购买完成 %@", transaction.payment.productIdentifier);
                if (self.CheckAfterPay) {  // 需要向苹果服务器验证一下
                    //通知代理
                    [self.delegate IAPToolBeginCheckingdWithProductID:transaction.payment.productIdentifier];
                    // 验证购买凭据
                    [self verifyPruchaseWithID:transaction.payment.productIdentifier];
                } else {  // 不需要向苹果服务器验证
                    //通知代理
                    [self.delegate IAPToolBoughtProductSuccessedWithProductID:transaction.payment.productIdentifier
                                                                    andInfo:nil];
                }
                // 将交易从交易队列中删除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            } else if (SKPaymentTransactionStateRestored == transaction.transactionState) { // 状态是恢复成功
                NSLog(@"恢复成功 :%@", transaction.payment.productIdentifier);
                // 通知代理
                [self.delegate IAPToolRestoredProductID:transaction.payment.productIdentifier];
                // 将交易从交易队列中删除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            } else if (SKPaymentTransactionStateFailed == transaction.transactionState) {  // 状态是交易失败
                NSLog(@"交易失败");
                // 将交易从交易队列中删除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                // 通知代理
                [self.delegate IAPToolCanceldWithProductID:transaction.payment.productIdentifier];
            } else if (SKPaymentTransactionStatePurchasing == transaction.transactionState) { // 状态是购买中ing....
                NSLog(@"正在购买");
            } else {
                NSLog(@"state:%ld",(long)transaction.transactionState); // 状态是已经购买
                NSLog(@"已经购买");
                // 将交易从交易队列中删除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            }
        }
    }
    
    #pragma mark - 恢复商品
    
    - (void)restorePurchase {
        // 恢复已经完成的所有交易.(仅限永久有效商品)
        [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
    }
    
    #pragma mark 验证购买凭据
    
    - (void)verifyPruchaseWithID:(NSString *)ProductID {
        // 验证凭据,获取到苹果返回的交易凭据
        // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
        NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
        // 从沙盒中获取到购买凭据
        NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
        // 发送网络POST请求,对购买凭据进行验证
        //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
        //In the real environment, use https://buy.itunes.apple.com/verifyReceipt
        // Create a POST request with the receipt data.
        NSURL *url = [NSURL URLWithString:checkURL];
        // 国内访问苹果服务器比较慢,timeoutInterval需要长一点
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0f];
        request.HTTPMethod = @"POST";
        // 在网络中传输数据,大多情况下是传输的字符串而不是二进制数据
        // 传输的是BASE64编码的字符串
        /**
         BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
         BASE64是可以编码和解码的
         */
        NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
        NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
        NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
        request.HTTPBody = payloadData;
        // 提交验证请求,并获得官方的验证JSON结果
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    #pragma clang diagnostic pop
        //    NSData *result = [self sendSynchronousRequest:request returningResponse:nil error:nil];
        
        // 官方验证结果为空
        if (result == nil) {
            NSLog(@"凭据验证失败");
            //验证失败,通知代理
            [self.delegate IAPToolCheckFailedWithProductID:ProductID
                                                   andInfo:result];
        }
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result
                                                             options:NSJSONReadingAllowFragments error:nil];
        NSLog(@"凭据-RecivedVerifyPruchaseDict:%@", dict);
        if (dict != nil) {
            // 验证成功,通知代理
            // bundle_id&application_version&product_id&transaction_id
            [self.delegate IAPToolBoughtProductSuccessedWithProductID:ProductID
                                                              andInfo:dict];
        } else {
            //验证失败,通知代理
            [self.delegate IAPToolCheckFailedWithProductID:ProductID
                                                   andInfo:result];
        }
    }
    
    - (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error {
        
        NSError __block *err = NULL;
        NSData __block *data;
        BOOL __block reqProcessed = false;
        NSURLResponse __block *resp;
        [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable _data, NSURLResponse * _Nullable _response, NSError * _Nullable _error) {
            resp = _response;
            err = _error;
            data = _data;
            reqProcessed = true;
        }] resume];
        while (!reqProcessed) {
            [NSThread sleepForTimeInterval:0];
        }
        *response = resp;
        *error = err;
        return data;
    }
    
    @end
    
    二、调用内购支付工具
    1. 导入内购工具#import "SIAPManager.h"
    2. 遵守内购协议@interface QuestionPayAlertView() <SIAPManagerDelegate>
    3. 在支付时,创建支付单例
    - (void)okButtonAction {
        SIAPManager *IAPManager = [SIAPManager defaultTool];
        IAPManager.delegate = self;
        // 支付前首先调用接口,获取商品ID数组,也就是内购列表
        [IAPManager requestProductsWithProductArray:self.productsIDArray];
    }
    
    1. 实现内购方法
    #pragma mark - 内购回调方法
    
    // 系统错误
    - (void)IAPToolSysWrong {
        [SVProgressHUD showInfoWithStatus:@"系统错误"];
        [SVProgressHUD dismissWithDelay:1.5];
    }
    
    // 购买成功
    - (void)IAPToolBoughtProductSuccessedWithProductID:(NSString *)productID andInfo:(NSDictionary *)infoDic {
        // 验证凭据,获取到苹果返回的交易凭据
        // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
        NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
        // 从沙盒中获取到购买凭据
        NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
        
        // 发送网络POST请求,对购买凭据进行验证
        //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
        //In the real environment, use https://buy.itunes.apple.com/verifyReceipt
        NSURL *url = [NSURL URLWithString:checkURL];
        // 国内访问苹果服务器比较慢,timeoutInterval需要长一点
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0f];
        
        request.HTTPMethod = @"POST";
        
        // 在网络中传输数据,大多情况下是传输的字符串而不是二进制数据
        // 传输的是BASE64编码的字符串
        /**
         BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
         BASE64是可以编码和解码的
         */
        NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
        //    NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
        // 创建订单
        [HttpManager creatOrderWithAmount:@(1) refer_id:@(self.refer_id) refer_type:@([self.referType integerValue]) course_price_id:@([self.coursePriceIDArray[self.selectIndex] integerValue]) receipt:encodeStr andHandle:^(NSString *error, NSDictionary *result) {
            [self.loadingHUD removeFromSuperview];
            if (error == nil) {
                [SVProgressHUD showSuccessWithStatus:@"购买成功!"];
                [SVProgressHUD dismissWithDelay:3.0 completion:^{
                // 购买成功后刷新列表,回调
                    // [self dismissAlertViewAction];
                    self.backgroundColor = [UIColor clearColor];
                    self.cancleButton.alpha = 0;
                    self.mainView.alpha = 0;
                    [self removeFromSuperview];
                    // 回调
                    if (self.delegate != nil && [self.delegate respondsToSelector:@selector(questionPaySuccess)]) {
                        [self.delegate questionPaySuccess];
                    }
                }];
            } else {
                [self.loadingHUD removeFromSuperview];
                [SVProgressHUD showErrorWithStatus:@"购买失败"];
                [SVProgressHUD dismissWithDelay:1.5];
            }
        }];
    }
    
    // 取消购买
    - (void)IAPToolCanceldWithProductID:(NSString *)productID {
        [self.loadingHUD removeFromSuperview];
        [SVProgressHUD showInfoWithStatus:@"取消购买"];
        [SVProgressHUD dismissWithDelay:1.5];
    }
    
    // 购买成功,开始验证购买
    - (void)IAPToolBeginCheckingdWithProductID:(NSString *)productID {
        // 
    }
    
    // 验证失败
    - (void)IAPToolCheckFailedWithProductID:(NSString *)productID andInfo:(NSData *)infoData {
        [self.loadingHUD removeFromSuperview];
        [SVProgressHUD showInfoWithStatus:@"购买失败"];
        [SVProgressHUD dismissWithDelay:1.5];
    }
    
    // 重复验证
    - (void)IAPToolCheckRedundantWithProductID:(NSString *)productID {
        [self.loadingHUD removeFromSuperview];
        [SVProgressHUD showInfoWithStatus:@"购买失败"];
        [SVProgressHUD dismissWithDelay:1.5];
    }
    
    // 已刷新可购买商品
    - (void)IAPToolGotProducts:(NSMutableArray *)products {
        [SIAPManager defaultTool].CheckAfterPay = NO;
        // mkj00006
    //    [[SIAPManager defaultTool] buyProduct:@"mlt_book_year"];
        [[SIAPManager defaultTool] buyProduct:self.productsIDArray[self.selectIndex]];
    }
    
    // 恢复了已购买的商品(永久性商品)
    - (void)IAPToolRestoredProductID:(NSString *)productID {
        [self.loadingHUD removeFromSuperview];
    }
    
    三、补充,Xcode开启内购权限
    开启内购权限.jpg

    相关文章

      网友评论

          本文标题:iOS集成苹果内购

          本文链接:https://www.haomeiwen.com/subject/hocxhhtx.html