ios ~ 内购:向服务器发送凭据,服务器和App Store校验

内购最新流程:仅限使用swift:https://juejin.cn/post/6974733392260644895

【iOS 内购:自动订阅功能】

1、第一步:调用苹果支付

#pragma mark -- 苹果支付
- (void)ApplePay {
    if ([SKPaymentQueue canMakePayments]) { 
        // 6.请求苹果后台商品
        [self getRequestAppleProduct];
    }
}

// 请求苹果商品
- (void)getRequestAppleProduct {
    // 7. 这里的com.czchat.CZChat01就对应着苹果后台的商品ID,他们是通过这个ID进行联系的。
    NSArray *product = [[NSArray alloc] initWithObjects:self.BuyID, nil];
    
    NSSet *nsset = [NSSet setWithArray:product];
    
    // 8.初始化请求
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
    request.delegate = self;
    
    [SVProgressHUD showWithStatus:@"正在加载"];
    // 9.开始请求
    [request start];
}

// 10.接收到产品的返回信息,然后用返回的商品信息进行发起购买请求
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSArray *products = response.products;
    
    // 如果服务器没有产品
    if (products.count == 0) {
        NSLog(@"nothing");
        return;
    }
    
    SKProduct *requestProduct = nil;
    for (SKProduct *pro in products) {
        
        NSLog(@"%@", [pro description]);
        NSLog(@"%@", [pro localizedTitle]);
        NSLog(@"%@", [pro localizedDescription]);
        NSLog(@"%@", [pro price]);
        NSLog(@"%@", [pro productIdentifier]);
        
        // 11. 如果后台消费条目的ID与我这里需要的请求的一样(用于确保订单的正确性)
        if ([pro.productIdentifier isEqualToString:self.BuyID]) {
            requestProduct = pro;
        }
    }
    NSLog(@"%@", requestProduct);
    
    //12. 发送购买请求
    NSArray *transactions = [SKPaymentQueue defaultQueue].transactions;
    if (transactions.count > 0) {
        // 检测是否有未完成的交易
        SKPaymentTransaction *transaction = [transactions firstObject];
        if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            return;
        } else {
            
            SKPayment *payment = [SKPayment paymentWithProduct:requestProduct];
            [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
            [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
            
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
        
    } else {
        SKPayment *payment = [SKPayment paymentWithProduct:requestProduct];
        [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
    
}

// 请求失败
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
    NSLog(@"%@", error);
    [SVProgressHUD dismiss];
}

// 反馈请求的产品信息结束后
- (void)requestDidFinish:(SKRequest *)request {
    NSLog(@"信息反馈结束");
}

// 13.购买监听结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
    for (SKPaymentTransaction *transaction in transactions) {
        NSLog(@"------");
        
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:
                NSLog(@"交易完成");
                NSLog(@"购买完成,向自己的服务器验证------ %@", transaction.payment.applicationUsername);
                NSLog(@"购买完成,向自己的服务器验证------ %@", transaction.payment.hyb_toJsonString);

                // 将交易从交易队列中删除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                
                if (transaction.originalTransaction) { // 订阅特殊处理
                    // 如果是自动续费的订单originalTransaction会有内容
                    
                } else {
                    //普通购买,以及 第一次购买 自动订阅

//                [self completedTransaction:transaction]; // 本地校验交易凭据
                    [self getApplePayDataToServerRequsetWith:transaction]; // 后台服务器校验App Store交易凭据
                }
               
                break;
            case SKPaymentTransactionStatePurchasing:
                NSLog(@"商品添加进列表");
                break;
            case SKPaymentTransactionStateRestored:
                NSLog(@"已经购买过商品");
                [SVProgressHUD dismiss];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                if (transaction.error.code == SKErrorPaymentCancelled) {
                    NSLog(@"用户取消了购买请求");
                } else if (transaction.error.code == SKErrorCloudServiceNetworkConnectionFailed) {
                    NSLog(@"设备无法链接到网络");
                }
                else {
                    NSLog(@"交易失败!!!");
                }
                [SVProgressHUD dismiss];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateDeferred:
                NSLog(@"已在队列中,等待外部操作");
                break;
            default:
                break;
        }
    }
}

2、第二步:后台服务器端验证App Store票据(为了安全,需要自己服务器验证App Store票据)

注意:字符串:@"{"receipt-data" : "%@"}",服务器解码时,带上“receipt-data”字段,不然base64解码失败

苹果返回的订单中in_app是一个数组,要想获取到对应的某一个订单信息,就如下这样:
特别说明:如果你需要当前票据的唯一号,取in_app中最后一个票据的transaction_id就行,即transaction.transactionIdentifier,这个transaction_id和苹果返回的订单中的transaction_id比较,获取对应的订单,改变数据库用户的交易记录。

#pragma mark -- -- 后台服务器端验证App Store票据  -- --
/** 注意:这里之后可以不用自己去验证,直接调用自己服务器接口,让后台去APP Store 验证*/
/** 将App Store返回的交易凭据发送到后台服务器,由后台服务器验证,code==200,提示成功 */
// (14.): 交易结束,当交易结束后还要后台服务器端去App Store上验证支付信息是否都正确,只有所有都正确后,我们就可以给用户发放我们的虚拟产品了。
- (void)getApplePayDataToServerRequsetWith:(SKPaymentTransaction *)transaction {
    // 唯一transaction_id
    NSString *transaction_id = transaction.transactionIdentifier;

    NSString *str = [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
    NSLog(@"------ 完成交易调用的方法getApplePayDataToServerRequsetWith 1----------");
    
    // 获取设备端app的交易收据数据,使用NSBundle的方法定位app的收据,并用Base64编码。将此 Base64 编码数据发送到您的服务器。
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
    
    
    
    if (!receiptData) {
        NSLog(@"没有交易收据, NO receipt");
    } else {
        // 获取编码格式的收据
        NSString *encodedReceipt = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
        
        // 注意:字符串:@"{\"receipt-data\" : \"%@\"}",服务器解码时,带上“receipt-data”字段,不然base64解码失败
        // NSString *sendString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodedReceipt];
        
        // server解析json字符串和端上一样,不带转义字符“\”根本解析不出来
        // 先将凭据转成字典,在将字典转成json字符串(添加了receipt-data)
        NSDictionary *sendDic = @{@"receipt-data" : encodedReceipt};
        NSData *data = [NSJSONSerialization dataWithJSONObject:sendDic options:NSJSONWritingPrettyPrinted error:nil];
        NSString *sendString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        
        [self getApplePayDataToServerRequestString:sendString withTransaction:transaction];
        
    }
    
    
}

//(15.):发送到服务器,由服务器判断交易收据(生成预订单,成功后在发送到服务器校验App Store凭据)
- (void)getApplePayDataToServerRequestString:(NSString *)receiptString withTransaction:(SKPaymentTransaction *)transaction {
    
    if(user_uuid == nil)
    {
        LoginViewCTRL *loginViewCTRL = [[LoginViewCTRL alloc] init];
        loginViewCTRL.loginVieCTRLBlock = ^{
        };
        [self.navigationController pushViewController:loginViewCTRL animated:YES];
        return;
    }
    
    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString *app_Version = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
    
    NSString *pay_member_id = @"";
    if ([self.BuyID isEqualToString:@"1MonthMember"]) {
        pay_member_id = @"21";
    }else if ([self.BuyID isEqualToString:@"6MontherMember"])
    {
        pay_member_id = @"23";
    }else if ([self.BuyID isEqualToString:@"12MonthMember"])
    {
        pay_member_id = @"24";
    }else if ([self.BuyID isEqualToString:@"buyMember03"])
    {
        pay_member_id = @"45";
    }
    
    NSDictionary *dic = @{
        @"user_uuid":wy_user_uuid?:@"",
        @"app_version":app_Version,
        @"pay_method":@"3",
        @"pay_source":@"6",
        @"pay_for":self.normalMemberBut.selected?@"1":@"11",
        @"client_type":@"2",
        @"pay_member_id":[NSString stringWithFormat:@"%@", pay_member_id]
    };
    NSArray *keyArray = [dic allKeys]; // 将dic中的全部key取出,并放到数组
    
    // 根据ASCII码,将参数key从小到大排序(升序)
    NSStringCompareOptions comparisonOptions =NSCaseInsensitiveSearch|NSNumericSearch|NSWidthInsensitiveSearch|NSForcedOrderingSearch;
    NSComparator sort = ^(NSString *obj1, NSString *obj2) {
        NSRange range = NSMakeRange(0, obj1.length);
        return [obj1 compare:obj2 options:comparisonOptions range:range];
    };
    NSArray *resultArr = [keyArray sortedArrayUsingComparator:sort];
//    NSLog(@"字符串数组排序结果%@",resultArr);
    
    NSMutableArray *paramValueArr = [NSMutableArray arrayWithCapacity:resultArr.count];
    for (NSString *str in resultArr) {
        // 将key对应的value,存到数组,用“7500KM”组成字符串
        NSString *tokenStr = [dic objectForKey:[NSString stringWithFormat:@"%@", str]];
        if (tokenStr.length > 0) {
            [paramValueArr addObject:tokenStr];
        }
    }
    
//    NSLog(@"字符串数组Value排序结果%@",paramValueArr);
    NSString *token = [paramValueArr componentsJoinedByString:@"7500KM"];
    token = [JXUtilTool md5HexDigest:token];
    
    NSDictionary *params = @{
        @"user_uuid":wy_user_uuid?:@"",
        @"app_version":app_Version,
        @"pay_method":@"3", // 支付方式 1:阿里 2:微信 3:苹果
        @"pay_source":@"6",// 1.二维码支付 2.H5支付 3.支付宝手机网站支付 4.支付宝电脑网站支付 5.微信JSAPI 6.APP
        @"pay_for":self.normalMemberBut.selected?@"1":@"11",// 1: 会员 2:金币 3:礼品卡 4:商城 10其它(自定义生成的订单)11阅读会员
        @"client_type":@"2", // 1.安卓 2ios 3.pc
        @"pay_member_id":[NSString stringWithFormat:@"%@", pay_member_id], // 会员类型:1月、6月、12月、一年
        @"token":[NSString stringWithFormat:@"%@", token]
    };
    
    __weak typeof(self)weakSelf = self;
    [[AFShareManager sharedManager] POST:[AFShareManager LFT_UrlString:@"/pay/member"] parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
            
        } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            NSLog(@"%@", task.currentRequest.URL);
            NSInteger reCode = [[responseObject objectForKey:@"reCode"] integerValue];
            if (reCode == 200) {
//                responseObject = [Tool Handle7500kmresponseObject:responseObject]; // 解码
                NSDictionary *dataDic = [NSDictionary dictionary];
                dataDic = [responseObject objectForKey:@"data"];
                
                [self getApplePayDataToServerRequestString:receiptString withBookingOrderDictionary:dataDic withTransaction:transaction];
            } else {
                NSString *message = [responseObject objectForKey:@"reMessage"];
                [KAlertViewFactory showToastWithMessage:message];
                [SVProgressHUD dismiss];
            }
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            NSLog(@"%@", task.currentRequest.URL);
            NSLog(@"error ---->>>> %@", error);
            [SVProgressHUD dismiss];
        }];
    
}

// (16.)发送到后台服务器,校验App Store凭据
- (void)getApplePayDataToServerRequestString:(NSString *)receiptString withBookingOrderDictionary:(NSDictionary *)bookingOrderDic withTransaction:(SKPaymentTransaction *)transaction {
    
    NSDictionary *params = @{
        @"base64Receipt":receiptString,
        @"out_trade_no":[NSString stringWithFormat:@"%@", bookingOrderDic[@"out_trade_no"]],
        @"price":[NSString stringWithFormat:@"%@", bookingOrderDic[@"price"]],
        @"user_uuid":[NSString stringWithFormat:@"%@", bookingOrderDic[@"user_uuid"]]
    };
    
    [[AFShareManager sharedManager] POST:[AFShareManager LFT_UrlString:@"/member/verifyReceipt"] parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
            
        } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            NSLog(@"%@", task.currentRequest.URL);
            NSInteger reCode = [[responseObject objectForKey:@"reCode"] integerValue];
            if (reCode == 200) { // 验证完成,改变展示的信息
                [self GainPersonInfo];
                [[WL_Tool getInstance] GainUserInfo:self];
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"购买成功" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
                [alert show];
                
                
            } else {
                NSString *message = [NSString stringWithFormat:@"%@",[responseObject valueForKey:@"reMessage"]];
                // [KAlertViewFactory showToastWithMessage:message];
                
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
                [alert show];
            }
            
            [SVProgressHUD dismiss];
            [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            NSLog(@"%@", task.currentRequest.URL);
            NSLog(@"error ---->>>> %@", error);
            [SVProgressHUD dismiss];
            [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        }];
    
}

2、第二步:(第二种,不需要服务器验证,自己在客户端验证,不安全,容易被破解,导致赚钱少哦)

#pragma mark -- 方法(completedTransaction:)和方法(SubmitDataTrade_no: memberType: Transaction:)
// 14. 交易结束,当交易结束后还要去App Store上验证支付信息是否都正确,只有所有都正确后,我们就可以给用户发放我们的虚拟产品了。
- (void)completedTransaction:(SKPaymentTransaction *)transaction {
    
    NSString *str = [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
    
    NSString *environment = [self environmentForReceipt:str];
    NSLog(@"------ 完成交易调用的方法completedTransaction 1----------%@", environment);
    
    
    // 验证凭据,获取到苹果返回的交易凭据
    // appStoreReceiptURL iOS 7.0 增加的,购买交易完成后,会将凭据存放在该地址
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    // 从沙盒中获取到购买凭据
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
    /**
     20     BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
     21     BASE64 是可以解密的
     22     */
    NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    
    NSString *sendString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
    NSLog(@"______%@", sendString);
    
    /** 注意:这里之后可以不用自己去验证,直接调用自己服务器接口,让后台去APP Store 验证*/
    
    NSURL *storeUrl = nil;
    if ([environment isEqualToString:@"environment=Sandbox"]) {
        storeUrl = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
        
    } else {
        storeUrl = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
    }
    
    // 这个二进制数据由服务器进行验证:zl
    NSData *postData = [NSData dataWithBytes:[sendString UTF8String] length:[sendString length]];
    NSLog(@"++++++%@", postData);
    NSMutableURLRequest *connectionRequest = [NSMutableURLRequest requestWithURL:storeUrl];
    
    [connectionRequest setHTTPMethod:@"POST"];
    [connectionRequest setTimeoutInterval:50.0]; // 120.0 ----50.0zl
    [connectionRequest setCachePolicy:NSURLRequestUseProtocolCachePolicy];
    [connectionRequest setHTTPBody:postData];
    
    // 开始请求
    NSError *error = nil;
    NSData *responseData = [NSURLConnection sendSynchronousRequest:connectionRequest returningResponse:nil error:&error];
    
    if (error) {
        NSLog(@"验证购买过程中发生的错误,错误信息:%@", error.localizedDescription);
        return;
    }
    NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
    NSLog(@"请求成功后的数据:%@",dic);
    //这里可以等待上面请求的数据完成后并且state = 0 验证凭据成功来判断后进入自己服务器逻辑的判断,也可以直接进行服务器逻辑的判断,验证凭据也就是一个安全的问题。楼主这里没有用state = 0 来判断。
    //  [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    
    NSString *product = transaction.payment.productIdentifier;
    
    NSLog(@"transaction.payment.productIdentifier++++%@",product);
    
    if ([product length] > 0)
    {
        NSArray *tt = [product componentsSeparatedByString:@"."];
        
        NSString *bookid = [tt lastObject];
        
        if([bookid length] > 0)
        {
            
            NSLog(@"打印bookid%@",bookid);
            //这里可以做操作把用户对应的虚拟物品通过自己服务器进行下发操作,或者在这里通过判断得到用户将会得到多少虚拟物品,在后面([self getApplePayDataToServerRequsetWith:transaction];的地方)上传上面自己的服务器。
            NSDictionary *receipt = [dic valueForKey:@"receipt"];
            NSArray *in_app = [receipt valueForKey:@"in_app"];
            
            for (int i = 0; i<in_app.count; i++) {
                
                NSDictionary *tempDic = [in_app objectAtIndex:i];
                NSString *transaction_id = [tempDic valueForKey:@"transaction_id"];
                NSString *product_id = [tempDic valueForKey:@"product_id"];
                
                if ([product_id isEqualToString:@"wl_1MonthMember"]) {
                    [self SubmitDataTrade_no:transaction_id memberType:@"21" Transaction:transaction];
                }else if ([product_id isEqualToString:@"wl_6MontherMember"])
                {
                    [self SubmitDataTrade_no:transaction_id memberType:@"23" Transaction:transaction];
                }else if ([product_id isEqualToString:@"wl_12MonthMember"])
                {
                    [self SubmitDataTrade_no:transaction_id memberType:@"24" Transaction:transaction];
                }else if ([product_id isEqualToString:@"buyMember03"])
                {
                    [self SubmitDataTrade_no:transaction_id memberType:@"45" Transaction:transaction];
                }
                    
            }
            
        }
    }
    [SVProgressHUD dismiss];
    //此方法为将这一次操作上传给我本地服务器,记得在上传成功过后一定要记得销毁本次操作。调用[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    //[self getApplePayDataToServerRequsetWith:transaction];
}

-(void)SubmitDataTrade_no:(NSString *)trade_no memberType:(NSString *)memberType Transaction:(SKPaymentTransaction *)transaction
{
    
    NSString *user_uuid = [[NSUserDefaults standardUserDefaults] objectForKey:@"user_uuid"];
    if(user_uuid == nil)
    {
        LoginViewCTRL *loginViewCTRL = [[LoginViewCTRL alloc] init];
        loginViewCTRL.loginVieCTRLBlock = ^{
        };
        [self.navigationController pushViewController:loginViewCTRL animated:YES];
        
    }else
    {
        [SVProgressHUD showWithStatus:@"正在加载"];
        NSString *url=[NSString stringWithFormat:@"%@%@",ServerOtherUrl,@"/pay/applepay.do"];
        NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
        NSString *app_Version = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
        NSString *token = [NSString stringWithFormat:@"%@%@%@%@%@",user_uuid,trade_no,memberType,app_Version,token_check_bit];
        
        token = [JXUtilTool md5HexDigest:token];
        NSLog(@"user_uuid=%@&trade_no=%@&memberType=%@&app_version=%@&token=%@",user_uuid,trade_no,memberType,app_Version,token);
        NSDictionary *parameters=@{@"user_uuid":user_uuid,
                                   @"trade_no":trade_no,
                                   @"memberType":memberType,
                                   @"app_version":app_Version,
                                   @"token":token
                                   };
        //AFN管理者调用get请求方法
        [[AFShareManager sharedManager] GET:url parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
            
        } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            //请求成功返回数据 根据responseSerializer 返回不同的数据格式
            NSLog(@"%@",task.currentRequest.URL);
            NSInteger reCode = [[responseObject valueForKey:@"reCode"] integerValue];
            if (reCode == 200)
            {
                [self GainPersonInfo];
                [[WL_Tool getInstance] GainUserInfo:self];
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"购买成功" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
                [alert show];
                
                
            }else
            {
                NSString *message = [NSString stringWithFormat:@"%@",[responseObject valueForKey:@"reMessage"]];
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
                [alert show];
            }
            [SVProgressHUD dismiss];
            [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            //请求失败
            NSLog(@"%@",task.currentRequest.URL);
            NSLog(@"error-->%@",error);
            [SVProgressHUD dismiss];
            [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
            
        }];
    }
    
}

3、第三步:


// 结束后,一定要销毁
- (void)dealloc {
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

//  在第二步中调用(第二种,不安全的验证票据方法中):
- (NSString *)environmentForReceipt:(NSString *)str {
    
    str = [str stringByReplacingOccurrencesOfString:@"\r\n" withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@"\t" withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@"\"" withString:@""];
        
    NSArray *arr = [str componentsSeparatedByString:@";"];
    
    // 存储收据环境的变量
    NSString *environment = arr[2];
    return environment;
}

4、漏订单处理:

#pragma mark -- Apple内购:漏单情况
- (void)appleRequest {
    
    /** Apple内购:漏单情况 */
    //读取用户状态和配置信息到单例中
    if ([GWUserInfoContext sharedUserInfoContext].userInfo.token &&[GWUserInfoContext sharedUserInfoContext].userInfo.name.length !=0) {
//        GWUserInfoModel *model = [GWUserInfoContext sharedUserInfoContext].userInfo;
        NSString *user_id = [NSString stringWithFormat:@"%ld", [GWUserInfoContext sharedUserInfoContext].userInfo.userId];
        
        if (user_id != nil) {
            NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
            NSArray *receiptArray = [userDefaults objectForKey:[NSString stringWithFormat:@"%@Apple_ReceiptData_MemberArray", user_id]];
            if (receiptArray != nil) { // 本地有值
                if (receiptArray.count > 0) {
                    NSMutableArray *receipt_MutableArray = [NSMutableArray arrayWithCapacity:0];
                    receipt_MutableArray = [receiptArray mutableCopy];
                    
                    dispatch_async(dispatch_get_global_queue(0, 0), ^{
                        // 处理耗时操作的代码块...
                        for (NSDictionary *dic in receipt_MutableArray) {
                            [self getApplePayWithOrderDictionary:dic]; // 可以多次请求数据库数据
                        }
                        //通知主线程刷新
                        dispatch_async(dispatch_get_main_queue(), ^{
                            //回调或者说是通知主线程刷新,(这里可以保存、删除一下数据)

                        });
                    });
                }
            }
            
            [userDefaults synchronize];
        }
    }
}

// (11.)发送到后台服务器,校验App Store凭据
- (void)getApplePayWithOrderDictionary:(NSDictionary *)orderDic {
    
    
//    [GWUserInfoContext sharedUserInfoContext].userInfo = [GWUtilities GetNSUserDefaults];
    NSString *user_id = [NSString stringWithFormat:@"%ld", [GWUserInfoContext sharedUserInfoContext].userInfo.userId];
    
    NSString *payloadStr = [NSString stringWithFormat:@"%@", orderDic[@"receipt-data"]];
    
    NSDictionary *params = @{
        @"payload":[NSString stringWithFormat:@"%@", payloadStr],
        @"transactionId":[NSString stringWithFormat:@"%@", orderDic[@"transaction_id"]],
        @"oid":[NSString stringWithFormat:@"%@", orderDic[@"order_id"]]
    };
    [BRNetworkHepler postWithUrl:@"/golf/vip/iosPay" params:params headers:nil success:^(GWHttpBaseResponseModel *responseObject, NSString *message) {
        
        NSInteger code = responseObject.code;
        if (code == 200) {
            
            NSDictionary *dataDic = responseObject.data;
            NSString *order_id = [NSString stringWithFormat:@"%@", dataDic[@"oid"]];
            
            // 票据验证成功:
            NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
            NSArray *receiptArray = [userDefaults objectForKey:[NSString stringWithFormat:@"%@Apple_ReceiptData_MemberArray", user_id]];
            if (receiptArray != nil) { // 本地含有储存 票据
                
                NSMutableArray *receipt_MutableArray = [NSMutableArray arrayWithCapacity:0];
                receipt_MutableArray = [receiptArray mutableCopy];
                for (NSDictionary *dic in receipt_MutableArray) {
                    if ([[NSString stringWithFormat:@"%@", dic[@"order_id"]] isEqualToString:order_id]) {
                        [receipt_MutableArray removeObject:dic];
                    }
                }
                
                if (receipt_MutableArray.count > 0) {
                    NSArray *Apple_ReceiptData_MemberArray = [receipt_MutableArray copy];
                    [userDefaults setObject:Apple_ReceiptData_MemberArray forKey:[NSString stringWithFormat:@"%@Apple_ReceiptData_MemberArray", user_id]];
                } else {
                    [userDefaults removeObjectForKey:[NSString stringWithFormat:@"%@Apple_ReceiptData_MemberArray", user_id]];
                }
        
            } else {
                
            }
            [userDefaults synchronize];
            
        } else {
            NSLog(@"message ===>>> %@", responseObject.msg);
        }
        
    } failure:^(NSError *error, NSString *message) {
        NSLog(@"error ====>>>> %@", error);
        NSLog(@"message ===>>> %@", message);
    }];
}
一些相关网址:
1、https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/persisting_a_purchase?language=objc
2、https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/setting_up_the_transaction_observer_for_the_payment_queue?language=objc
3、iOS 内购(In-App Purchase)总结

https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/validating_receipts_with_the_app_store?language=objc

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

推荐阅读更多精彩内容