美文网首页
iOS——苹果内购工具类(掉单处理)

iOS——苹果内购工具类(掉单处理)

作者: Lucky_Blue | 来源:发表于2022-11-27 14:00 被阅读0次
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface SandBoxHelper : NSObject
    // 程序主目录,可见子目录(3个):Documents、Library、tmp
    + (NSString *)homePath;
    
    // 程序目录,不能存任何东西
    + (NSString *)appPath;
    
    // 文档目录,需要ITUNES同步备份的数据存这里,可存放用户数据
    + (NSString *)docPath;
    
    // 配置目录,配置文件存这里
    + (NSString *)libPrefPath;
    
    // 缓存目录,系统永远不会删除这里的文件,ITUNES会删除
    + (NSString *)libCachePath;
    
    // 临时缓存目录,APP退出后,系统可能会删除这里的内容
    + (NSString *)tmpPath;
    
    //用于存储iap内购返回的购买凭证
    + (NSString *)iapReceiptPath;
    
    //存储成功订单的方法
    +(NSString *)SuccessIapPath;
    
    //存储崩溃日志的方法;
    +(NSString *)crashLogInfo;
    
    //存储退出资源的路径
    +(NSString *)exitResourePath;
    
    //保存临时订单
    +(NSString *)tempOrderPath;
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "SandBoxHelper.h"
    
    @implementation SandBoxHelper
    + (NSString *)homePath {
        return NSHomeDirectory();
    }
    
    + (NSString *)appPath {
        NSArray * paths = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES);
        return [paths objectAtIndex:0];
    }
    
    + (NSString *)docPath {
        NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        return [paths objectAtIndex:0];
    }
    
    + (NSString *)libPrefPath {
        NSArray * paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
        return [[paths objectAtIndex:0] stringByAppendingFormat:@"/Preferences"];
    }
    
    + (NSString *)libCachePath {
        
        NSArray * paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
        return [[paths objectAtIndex:0] stringByAppendingFormat:@"/Caches"];
    }
    
    + (NSString *)tmpPath {
        return [NSHomeDirectory() stringByAppendingFormat:@"/tmp"];
    }
    
    + (NSString *)iapReceiptPath {
        
        NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/EACEF35FE363A75A"];
        [self hasLive:path];
        return path;
    }
    
    + (BOOL)hasLive:(NSString *)path
    {
        if ( NO == [[NSFileManager defaultManager] fileExistsAtPath:path] )
        {
            return [[NSFileManager defaultManager] createDirectoryAtPath:path
                                             withIntermediateDirectories:YES
                                                              attributes:nil
                                                                   error:NULL];
        }
        
        return YES;
    }
    
    +(NSString *)SuccessIapPath{
        
        NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/SuccessReceiptPath"];
        
        [self hasLive:path];
        
        
        return path;
        
    }
    
    +(NSString *)exitResourePath{
        
        NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/ExitResourePath"];
        
        [self hasLive:path];
        
        
        return path;
    }
    
    +(NSString *)tempOrderPath{
      
        NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/tempOrderPath"];
        
        [self hasLive:path];
       
        return path;
        
    }
    
    +(NSString *)crashLogInfo{
        
        NSString * path = [[self libPrefPath]stringByAppendingFormat:@"/crashLogInfoPath"];
        [self hasLive:path];
        
        return path;
    }
    @end
    
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    /**
     block
    
     @param isSuccess 是否支付成功
     @param certificate 支付成功得到的凭证(用于在自己服务器验证)
     @param errorMsg 错误信息
     */
    typedef void(^PayResult)(BOOL isSuccess, NSString *__nullable certificate,NSString * __nullable errorMsg);
    
    @interface IPAPurchase : NSObject
    /**
     *
     */
    @property (nonatomic, copy) PayResult payResultBlock;
    
    +(instancetype)manager;
    /**
    开启内购监听 在程序入口didFinishLaunchingWithOptions实现
    */
    -(void)startManager;
    /**
    停止内购监听 在AppDelegate.m中的applicationWillTerminate方法实现
    */
    -(void)stopManager;
    
    /**
    拉起内购支付
    @param productID 内购商品ID
    @param payResult 结果
    */
    -(void)buyProductWithProductID:(NSString *)productID payResult:(PayResult)payResult;
    
    
    /**
     *订单编号
     */
    @property (nonatomic, copy) NSString * orderNumber;
    /**
     *用户id
     */
    @property (nonatomic, copy) NSString * userid;
    /**
     *
     */
    @property (nonatomic, assign) CGFloat  tbCount;
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "IPAPurchase.h"
    #import <StoreKit/StoreKit.h>
    #import "SandBoxHelper.h"
    #import "NSString+Chinese.h"
    
    static NSString *const receiptKey = @"receiptKey";
    
    dispatch_queue_t iap_queue(){
        static dispatch_queue_t as_iap_queue;
        static dispatch_once_t onceToken_iap_queue;
        dispatch_once(&onceToken_iap_queue, ^{
            as_iap_queue = dispatch_queue_create("com.iap.queue", DISPATCH_QUEUE_CONCURRENT);
        });
        return as_iap_queue;
    }
    
    @interface IPAPurchase()
    <SKPaymentQueueDelegate, SKProductsRequestDelegate>
    /**
     *
     */
    @property (nonatomic, strong) SKProductsRequest * request;
    
    /**
     *购买凭证(存储base64编码的交易凭证)
     */
    @property (nonatomic, copy) NSString * receipt;
    /**
     *
     */
    @property (nonatomic, copy) NSString * productId;
    
    @end
    
    static IPAPurchase *manager = nil;
    
    @implementation IPAPurchase
    
    +(instancetype)manager{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            if(!manager){
                manager = [[IPAPurchase alloc] init];
            }
        });
        return manager;
    }
    #pragma mark -- 漏单处理
    -(void)startManager{
        dispatch_sync(iap_queue(), ^{
            [[SKPaymentQueue defaultQueue] addTransactionObserver:manager];
        });
    }
    #pragma mark -- 移除交易事件
    -(void)stopManager{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
        });
    }
    #pragma mark -- 发起购买的方法
    -(void)buyProductWithProductID:(NSString *)productID payResult:(PayResult)payResult{
        self.payResultBlock = payResult;
        [self removeAllUncompleteTransactionBeforeStartNewtransaction];
        //购买中HUD。。。
        [self showWhiteHUDWithText:@" 购买中... "];
        
        self.productId = productID;
        if(!self.productId.length){
            UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"温馨提示" message:@"没有对应的商品" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
    
            [alertView show];
        }
        
        if([SKPaymentQueue canMakePayments]){
            [self requestProductInfo:self.productId];
        }
        else{
            UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"温馨提示" message:@"请先开启应用内付费购买功能。" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
            [alertView show];
        }
    }
    #pragma mark -- 结束上次未完成的交易 防止串单
    -(void)removeAllUncompleteTransactionBeforeStartNewtransaction{
        NSArray *transactionsArray = [SKPaymentQueue defaultQueue].transactions;
        if(transactionsArray.count >0){
            //检测是否有未完成的交易
            SKPaymentTransaction *transaction = [transactionsArray firstObject];
            if(transaction.transactionState == SKPaymentTransactionStatePurchased){
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                return;
            }
        }
    }
    #pragma mark --发起购买请求
    -(void)requestProductInfo:(NSString *)productId{
        NSArray *productArray = [[NSArray alloc] initWithObjects:productId, nil];
        NSSet *IDSet = [NSSet setWithArray:productArray];
        self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:IDSet];
        self.request.delegate = self;
        [self.request start];
    }
    
    #pragma mark -- SKProductsRequestDelegate 查询成功后的回调
    -(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    //    [self dismissCustom];
        
        NSArray *myProduct = response.products;
        if(myProduct.count == 0){
            //没有该商品信息HUD。。。
            [self showErrorText:@"无法获取产品信息,购买失败"];
            
            if (self.payResultBlock) {
                self.payResultBlock(NO, nil, @"无法获取产品信息,购买失败");
            }
            return;
        }
        
        SKProduct *product = nil;
        for (SKProduct *pro in myProduct) {
            NSLog(@"SKProduct 描述信息%@", [pro description]);
            NSLog(@"产品标题 %@" , pro.localizedTitle);
            NSLog(@"产品描述信息: %@" , pro.localizedDescription);
            NSLog(@"价格: %@" , pro.price);
            NSLog(@"Product id: %@" , pro.productIdentifier);
            
            if([pro.productIdentifier isEqualToString:self.productId]){
                product = pro;
                break;
            }
        }
        
        if(product){
            SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
            payment.applicationUsername = self.orderNumber;
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
        else{
             NSLog(@"没有此商品");
        }
    }
    //查询失败后的回调
    -(void)request:(SKRequest *)request didFailWithError:(NSError *)error{
        [self dismissCustom];
        if (self.payResultBlock) {
            self.payResultBlock(NO, nil, [error localizedDescription]);
        }
    }
    ////如果没有设置监听购买结果将直接跳至反馈结束
    -(void)requestDidFinish:(SKRequest *)request{
    //    [self dismissCustom];
    }
    #pragma mark -- 监听结果
    -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
        
        //当用户购买的操作有结果时,就会触发下面的回调函数,
        for (SKPaymentTransaction * transaction in transactions) {
            
            switch (transaction.transactionState) {
                case SKPaymentTransactionStatePurchased:{
                    [self completeTransaction:transaction];
    //                [self dismissCustom];
                }break;
    
                case SKPaymentTransactionStateFailed:{
                    [self failedTransaction:transaction];
                    [self dismissCustom];
                }break;
    
                case SKPaymentTransactionStateRestored:{//已经购买过该商品
                    [self restoreTransaction:transaction];
                    [self dismissCustom];
                }break;
    
                case SKPaymentTransactionStatePurchasing:{
                    NSLog(@"正在购买中...");
                }break;
    
                case SKPaymentTransactionStateDeferred:{
                    NSLog(@"最终状态未确定");
                    [self dismissCustom];
                }break;
    
                default:
                    break;
              }
        }
    }
    //完成交易
    #pragma mark -- 交易完成的回调
    - (void)completeTransaction:(SKPaymentTransaction *)transaction{
        NSLog(@"购买成功,准备验证发货");
        [self getReceipt]; //获取交易成功后的购买凭证
        [self saveReceipt:transaction]; //存储交易凭证
        [self checkIAPFiles:transaction];
    }
    
    #pragma mark -- 处理交易失败回调
    - (void)failedTransaction:(SKPaymentTransaction *)transaction
    {
        NSString *error = nil;
        if(transaction.error.code != SKErrorPaymentCancelled) {
            //购买失败HUD。。。
            [self showSuccessText:@"购买失败"];
        } else {
            //取消购买HUD。。。
            [self showSuccessText:@"取消购买"];
        }
    
        if (self.payResultBlock) {
            self.payResultBlock(NO, nil, error);
        }
        [[SKPaymentQueue defaultQueue]finishTransaction:transaction];
    }
    
    - (void)restoreTransaction:(SKPaymentTransaction *)transaction{
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    }
    
    #pragma mark -- 获取购买凭证
    -(void)getReceipt{
        NSURL * receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
        NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl];
        NSString * base64String = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
        self.receipt = base64String;
    }
    
    #pragma mark -- 存储购买凭证
    -(void)saveReceipt:(SKPaymentTransaction *)transaction{
        NSString * userId;
        NSString * order;
    
        if (self.userid) {
            userId = self.userid;
            [[NSUserDefaults standardUserDefaults]setObject:userId forKey:@"unlock_iap_userId"];
        }else{
    
            userId = [[NSUserDefaults standardUserDefaults]objectForKey:@"unlock_iap_userId"];
        }
    
        order = transaction.payment.applicationUsername;
    
        NSString *fileName = [NSString UUID];
        NSString *savedPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper iapReceiptPath], fileName];
        NSMutableDictionary * dic = [[NSMutableDictionary alloc]init];
        [dic setValue: self.receipt forKey:receiptKey];
        [dic setValue: userId forKey:@"user_id"];
        [dic setValue: order forKey:@"order"];
        BOOL ifWriteSuccess  = [dic writeToFile:savedPath atomically:YES];
    
        if (ifWriteSuccess) {
            NSLog(@"购买凭据存储成功!");
        }
    }
    
    #pragma mark -- 验证本地数据
    -(void)checkIAPFiles:(SKPaymentTransaction *)transaction{
    
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSError *error = nil;
        NSArray *cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];
        if (error == nil) {
        for (NSString *name in cacheFileNameArray) {
    
            if ([name hasSuffix:@".plist"]){ //如果有plist后缀的文件,说明就是存储的购买凭证
    
                NSString *filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];
    
                [self sendAppStoreRequestBuyPlist:filePath trans:transaction];
            }
        }
    
        } else {
    
        }
    }
    
    #pragma mark -- 存储成功订单
    -(void)SaveIapSuccessReceiptDataWithReceipt:(NSString *)receipt Order:(NSString *)order UserId:(NSString *)userId{
        NSMutableDictionary * mdic = [[NSMutableDictionary alloc]init];
        [mdic setValue:[self getCurrentZoneTime] forKey:@"time"];
        [mdic setValue: order forKey:@"order"];
        [mdic setValue: userId forKey:@"userid"];
        [mdic setValue: receipt forKey:receiptKey];
        NSString *fileName = [NSString UUID];
        NSString * successReceiptPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper SuccessIapPath], fileName];
        //存储购买成功的凭证
        [mdic writeToFile:successReceiptPath atomically:YES];
    }
    
    #pragma mark -- 获取系统时间的方法
    -(NSString *)getCurrentZoneTime{
        NSDate * date = [NSDate date];
        NSDateFormatter*formatter = [[NSDateFormatter alloc]init];
        [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
        NSString*dateTime = [formatter stringFromDate:date];
        return dateTime;
    }
    
    #pragma mark -- 去服务器验证购买
    -(void)sendAppStoreRequestBuyPlist:(NSString *)plistPath trans:(SKPaymentTransaction *)transaction{
    //    [self showWhiteHUDWithText:@"验证中..."];
        NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
        NSString * receipt = [dic objectForKey:receiptKey];
        NSString * order = [dic objectForKey:@"order"];
        NSString * userId = [dic objectForKey:@"user_id"];
    
    #pragma mark -- 发送信息去验证是否成功
        WeakSelf
        [JHNetworkHelper requestPOST:@"iapPay/yz_iappay" parameters:@{@"orderNo":order,@"receipt":receipt} modelClass:nil success:^(id responseObject) {
            [weakSelf showSuccessText:@"购买成功"];
            weakSelf.tbCount = [responseObject[@"result"][@"money"] floatValue];
            
            [[NSUserDefaults standardUserDefaults]removeObjectForKey:@"unlock_iap_userId"];
            NSData * data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
    
            NSString *result = [data base64EncodedStringWithOptions:0];
    
            if (weakSelf.payResultBlock) {
                weakSelf.payResultBlock(YES, result, nil);
            }
            //这里将成功但存储起来
            [weakSelf SaveIapSuccessReceiptDataWithReceipt:receipt Order:order UserId:userId];
    
            [weakSelf successConsumptionOfGoodsWithOrder:order];
            
        } failure:^(NSError *error) {
            [weakSelf dismissCustom];
        }];
    //    [[ULSDKAPI shareAPI] sendVertifyWithReceipt:receipt order:order success:^(ULSDKAPI *api, id responseObject) {
    //
    //        if (RequestSuccess) {
    //
    //            NSLog(@"服务器验证成功!");
    //
    //            [[SKPaymentQueue defaultQueue]finishTransaction:transaction];
    //
    //            [RRHUD hide];
    //
    //            [RRHUD showSuccessWithContainerView:UL_rootVC.view status:NSLocalizedString(@"购买成功", @"")];
    //
    //            [[NSUserDefaults standardUserDefaults]removeObjectForKey:@"unlock_iap_userId"];
    //            NSData * data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
    //
    //            NSString *result = [data base64EncodedStringWithOptions:0];
    //
    //            if (self.payResultBlock) {
    //                self.payResultBlock(YES, result, nil);
    //            }
    //            //这里将成功但存储起来
    //            [self SaveIapSuccessReceiptDataWithReceipt:receipt Order:order UserId:userId];
    //
    //            [self successConsumptionOfGoodsWithOrder:order];
    //
    //        }else{
    //          //在这里向服务器发送验证失败相关信息
    //    } failure:^(ULSDKAPI *api, NSString *failure) {
    //
    //    }
    }
    
    #pragma mark -- 根据订单号来移除本地凭证的方法
    -(void)successConsumptionOfGoodsWithOrder:(NSString * )cpOrder{
    
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSError * error;
        if ([fileManager fileExistsAtPath:[SandBoxHelper iapReceiptPath]]) {
    
            NSArray * cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];
    
            if (error == nil) {
    
                for (NSString * name in cacheFileNameArray) {
    
                    NSString * filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];
    
                    [self removeReceiptWithPlistPath:filePath ByCpOrder:cpOrder];
    
                }
            }
        }
    }
    
    #pragma mark -- 根据订单号来删除 存储的凭证
    -(void)removeReceiptWithPlistPath:(NSString *)plistPath   ByCpOrder:(NSString *)cpOrder{
    
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSError * error;
        NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
        NSString * order = [dic objectForKey:@"order"];
    
        if ([cpOrder isEqualToString:order]) {
            //移除与用户id订单号一样的plist 文件
            BOOL ifRemove =  [fileManager removeItemAtPath:plistPath error:&error];
            if (ifRemove) {
                NSLog(@"成功订单移除成功");
            }else{
                NSLog(@"成功订单移除失败");
            }
        }else{
            NSLog(@"本地无与之匹配的订单");
        }
    }
    @end
    
    

    相关文章

      网友评论

          本文标题:iOS——苹果内购工具类(掉单处理)

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