iOS集成苹果内购

2020-03-02  本文已影响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
上一篇下一篇

猜你喜欢

热点阅读