美文网首页iOS收藏固予SDK
总结一下开发苹果内购IAP和防丢单的一些措施

总结一下开发苹果内购IAP和防丢单的一些措施

作者: 蛋壳儿 | 来源:发表于2017-10-11 19:08 被阅读1252次
    apple.jpg

    因为之前做的项目都是电商的,交易的也都是实体的,所以都会采用微信,支付宝等第三方支付,现在这个公司的项目是个付费小说的软件,要搞支付只能选择苹果内购了。
    具体怎么在ituens connect开通就不在介绍了,网上也有很多资料,这篇文章主要分享一下app接入内购的代码,已经防丢单的处理策略。

    下面先介绍一下 内购的主要流程

    1. 首先拿到productid去请求苹果服务器,请求产品信息
    2. 得到苹果服务器的返回信息,如果产品存在且为可售状态,则生成内购订单,加入到苹果的交易队列
    3. 苹果检查到支付成功或者失败后,会通过代理,回调支付的结果
    4. 根据结果,如果成功则取出交易凭证,一般是交给自己的服务器去验证
    5. 验证完成之后的一些处理
    // 去苹果服务器请求产品信息
    - (void)requestProductData:(NSString *)productId {
        NSArray *productArr = [[NSArray alloc]initWithObjects:productId, nil];
        NSSet *productSet = [NSSet setWithArray:productArr];
        SKProductsRequest *request = [[SKProductsRequest alloc]initWithProductIdentifiers:productSet];
        request.delegate = self;
        [request start];
        
    #pragma mark - SKProductsRequestDelegate //这个是向苹果服务器请求产品的代理
    
    - (void)requestDidFinish:(SKRequest *)request
    {
        _timeIntervalStart = [[NSDate date] timeIntervalSince1970];
        [KR_RECHARGELOG appendingString:@"-> SKProductReqFinish"];
    }
    
    - (void)request:(SKRequest *)request didFailWithError:(NSError *)error
    {
        [KR_PROGRESS toast:STRING_LOCALIZE(@"recharge_pay_failed")];
        [KR_RECHARGELOG appendingString:@"-> SKProductReqFail"];
    }
    
    /**
     收到产品返回信息
     */
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
        NSArray *productArr = response.products;
        if ([productArr count] == 0)
        {
            return;
        }
        
        SKProduct *product = nil;
        for (SKProduct *pro in productArr) {
            if ([pro.productIdentifier isEqualToString:_orderModel.data[@"product_id"]]) {
                product = pro;
                break;
            }
        }
        if (product)
        {
            //此处为创建内购订单,加入到苹果内购iap的交易队列
            SKMutablePayment * payment = [SKMutablePayment paymentWithProduct:product];
            //此处是把订单号 存到 payment的applicationUsername字段上
            payment.applicationUsername = _orderModel.orderNum;
            //发送内购请求
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
        else
        {
            NSLog(@"没有此商品");
        }
    }
    #pragma mark - SKPaymentTransactionObserver
    // 监听购买结果
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
    {
        for (SKPaymentTransaction *transaction in transactions)
        {
            switch (transaction.transactionState)
            {
                case SKPaymentTransactionStatePurchased://交易完
                    [self completeTransaction:transaction];
                    break;
                case SKPaymentTransactionStateFailed://交易失败
                    [self failedTransaction:transaction];
                    break;
                case SKPaymentTransactionStateRestored://已经购买过该商品
                    [self restoreTransaction:transaction];
                    break;
                case SKPaymentTransactionStatePurchasing://商品添加进列表
                    break;
                case SKPaymentTransactionStateDeferred://状态未确定
                default:
                    break;
            }
        }
    }
    
    - (void)failedTransaction:(SKPaymentTransaction *)transaction {
        //交易失败
        [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    }
    
    - (void)restoreTransaction:(SKPaymentTransaction *)transaction
    {
        [KR_PROGRESS toast:STRING_LOCALIZE(@"recharge_reinstate")];
        // 对于已购商品,处理恢复购买的逻辑
        [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    }
    
    // 验证购买
    - (void)completeTransaction:(SKPaymentTransaction *)transaction {
        // 验证凭据,获取到苹果返回的交易凭据
        // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
        NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
        // 从沙盒中获取到购买凭据
        NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
       //这个recptData就是交易的凭证,你可以选择是客户端自己去向苹果验证,但更多情况是 把凭证传给服务器,让服务器去验证
    }
    

    在开发测试的时候可以先客户端验证,先验证能不能完整走完内购流程

    //沙盒测试环境验证
    #define SANDBOX @"https://sandbox.itunes.apple.com/verifyReceipt"
    //正式环境验证
    #define AppStore @"https://buy.itunes.apple.com/verifyReceipt"
    // 验证购买
    - (void)verifyPurchaseWithPaymentTrasaction {
    
        // 验证凭据,获取到苹果返回的交易凭据
        // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
        NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
        // 从沙盒中获取到购买凭据
        NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
    
    
        // 发送网络POST请求,对购买凭据进行验证
        //测试验证地址:https://sandbox.itunes.apple.com/verifyReceipt
        //正式验证地址:https://buy.itunes.apple.com/verifyReceipt
        NSURL *url = [NSURL URLWithString:SANDBOX];
        NSMutableURLRequest *urlRequest =
        [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
        urlRequest.HTTPMethod = @"POST";
        NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
        NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
        NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
        urlRequest.HTTPBody = payloadData;
        // 提交验证请求,并获得官方的验证JSON结果 iOS9后更改了另外的一个方法
        NSData *result = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:nil];
        // 官方验证结果为空
        if (result == nil) {
            NSLog(@"验证失败");
            return;
        }
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingAllowFragments error:nil];
        if (dict != nil) {
            // 比对字典中以下信息基本上可以保证数据安全
            // bundle_id , application_version , product_id , transaction_id
            //        NSLog(@"验证成功!购买的商品是:%@", @"_productName");
    
            NSLog(@"验证成功%@",dict);
        }
    }
    

    以上基本上就是内购的流程了,但是有可能由于个种原因,导致苹果内购的- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions回调不及时,或者当次根本就没回调成功,等等照成订单丢失的现象。
    下面我结合我们项目的实际情况来说一下对app内购丢单的处理,我们的app逻辑是在下单的同时,后台会给你一个订单号,等app支付完成后,在苹果回调时,把凭证和订单号一块传给后台,如果后台验证成功,则会根据订单号增加对应的用户id的看点。刚上线的时候,每天都收到很多看点不到账的问题反馈,原因是因为苹果回调的不及时,等苹果回调了,订单号已经不存在了,所以造成,扣款成功,服务器却无法知道成功状态,导致订单丢失。
    后来我们进行了优化处理,比如在上述- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response方法中,将订单号保存到对应的SKMutablePayment,然后等到苹果内购回调的时候,去除订单号,然后将交易凭证和订单号一块发给服务器处理。
    还有就是在苹果回调的时候,将订单号以及凭证本地化存储,我做项目的时候采用的是,每有一个交易就存储到本地的plist文件数组中,在很多地方去主动检查有没有存储的订单号,如果有就依次取出去拿到服务器认证,认证完成之后在本地删除对应的凭证及订单号。
    经过优化之后投诉量基本上在几天一个,对内购丢单有疑问的同学可以互相交流一下

    相关文章

      网友评论

        本文标题:总结一下开发苹果内购IAP和防丢单的一些措施

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