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

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文件数组中,在很多地方去主动检查有没有存储的订单号,如果有就依次取出去拿到服务器认证,认证完成之后在本地删除对应的凭证及订单号。
经过优化之后投诉量基本上在几天一个,对内购丢单有疑问的同学可以互相交流一下

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

推荐阅读更多精彩内容