Apple 内购流程(代码开发级别)

Apple 内购流程:
1、向苹果服务器,发送请求,获取可购买商品信息。
2、SKProductsRequest 协议 。获取商品数据列表。
3、确定购买商品(或回复商品)。
4、监测交易过程。
5、验证交易收据(receipt)。
1、向苹果服务器,发送请求,获取可购买商品信息。

//提交商品信息请求
- (void)requestProductWithProductArray:(NSArray *)productIdAry {
     NSSet *set = [[NSSet alloc] initWithArray:productIdAry];
    
    //“异步”请求有哪些可售商品
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
    //启动服务
    [request start];
}

2、SKProductsRequest 协议 。获取商品数据列表。

#pragma mark - SKProductsRequest Delegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    //获取商品数据
    NSArray *productAry = response.products;
    if ([productAry count] <= 0) {
        //暂无商品信息
        return;
    }
    
    //初始化本地商品信息个数
    if (self.productDic == nil) {
        self.productDic = [NSMutableDictionary dictionaryWithCapacity:response.products.count];
    }

    //遍历商品数据
    for (SKproduct *product in response.products) {
        [self.productDis setObject:product forKey:product.productIdentifier];
        
        //打印产品列表信息
        NSLog(@"product ID : %@",product.productIdentifier);
        NSLog(@"product Description : %@",product.description);
        NSLog(@"product Title :%@",product.localizedTitle);
    }

#warning - Todo 可以将商品信息列表传给ViewController以供显示商品列表。
#warning - Todo 但不建议使用此信息显示商品列表,可以用此信息进行产品ID的验证。
}

#pragma mark - SKProductsRequest Delegate
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
    NSLog(@"---------------- 错误 ---------------");
}

#pragma mark - SKProductsRequest Delegate
- (void)requestDidFinish:(SKPRequest *)request {
    NSLog(@"--------------- 反馈信息结束 ---------------");
}

3、确定购买商品

#pragma mark - Buy Product
- (void)buyProduct:(NSString *)productID {
    //购买商品
    SKProduct *buyProduct = self.productDic[productID];

    SKPayment *payment = [SKPayment paymentWithProduct:buyProduct];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

4、回复商品

#pragma mark - Restore Product
- (void)restorePurchase {
    //回复已经完成的所有交易(仅限永久有效商品)。
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

5、监测交易过程

#pragma mark - SKPaymentTransaction Observer
- (void)paymentQueue:(SKPaymentQueue *)queue updateTransactions:(NSArray<SKPaymentTransaction *> *)transaction {
    for (SKPaymentTransaction *transaction in transactions) {
        NSLog(@"交易队列状态 :%@",transactions);
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:    //购买成功
                //  向苹果服务器进行验证
                if (self.checkAfterPay) {
                    //支付成功了,并开始向苹果服务器进行验证(若CheckAfterPay为NO,则不会经过此步骤)
                }else {
                    //商品完全购买成功且验证成功了。(若CheckAfterPay为NO,则会在购买成功后直接触发此方法)
                }
                //  目前都是全部进行验证。
                [self verifyPruchaseWithPaymentTransaction:transaction isTestServer:NO];
                break;
                
            case SKPaymentTransactionStatePurchasing:   //  正在购买
                NSLog(@"正在购买...");
                break;
                
            case SKPaymentTransactionStateRestored:     //  恢复成功
                //  将交易从交易队列中删除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
                
            case SKPaymentTransactionStateFailed:       //  购买取消或失败
                if (transaction.error.code != SKErrorPaymentCancelled) {
                    NSLog(@"购买失败!");
                }else{
                    NSLog(@"购买取消!");
                }
                //  将交易从交易队列中删除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            
            default:
                //  将交易从交易队列中删除
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
        }
    }
}

6、验证交易收据(receipt)

#pragma mark - Verify Receipt
- (void)verifyPruchaseWithPaymentTransaction:(SKPaymentTransaction *)transaction isTestServer:(BOOL)flag {
    //  验证凭据,获取到苹果返回的交易凭据
    //  appStore Receipt URL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    //  从沙盒中获取到购买凭据receipte
    NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
    
    
    if (!receipt) {
        //  交易凭证为空,验证失败
        //  KIAPPurchVerFailed
        return;
    }else {
        //  购买成功将交易凭证发送给服务端进行再次校验
        //  kIAppurchSuccess
    }
    
    // 在网络中传输数据,大多情况下是传输的字符串而不是二进制数据
    // 传输的是BASE64编码的字符串
    /**
     BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
     BASE64是可以编码和解码的
     */
    NSString *encodeStr = [receipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    NSString *encodeLoad = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
    NSData *receiptData = [encodeLoad dataUsingEncoding:NSUTF8StringEncoding];
    
    //  发送网络POST请求,对购买凭证进行验证
    //  In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
    //  In the real environment, use https://buy.itunes.apple.com/verifyReceipt
    NSString *serverStr = @"https://buy.itunes.apple.com/verifyReceipt";
    if (flag) {
        serverStr = @"https://sandbox.itunes.apple.com/verifyReceipt";
    }
    
    NSURL *storeURL = [NSURL URLWithString:serverStr];
    
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:receiptData];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [NSURLConnection sendAsynchronousRequest:storeRequest
                                       queue:queue
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                               if (connectionError) {
                                   // 无法连接服务器,购买校验失败
                                   // KIAPPurchVerFailed
                                   
                               } else {
                                   NSError *error;
                                   NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data
                                                                                                options:0
                                                                                                  error:&error];
                                   NSLog(@"----验证结果 %@",jsonResponse);
                                   if (!jsonResponse) {
                                       // 苹果服务器校验数据返回为空校验失败
                                       // KIAPPurchVerFailed
                                   }
                                   // 先验证正式服务器,如果正式服务器返回21007再去苹果测试服务器验证,沙盒测试环境苹果用的是测试服务器
                                   NSString *status = [NSString stringWithFormat:@"%@",jsonResponse[@"status"]];
                                   if (status && [status isEqualToString:@"21007"]) {
#warning - Todo 给ViewController提示
                                       [self verifyPruchaseWithPaymentTransaction:transaction isTestServer:YES];
                                       
                                   }else if(status && [status isEqualToString:@"0"]){
#warning - Todo 给ViewController提示
                                       //   kIAPPurchVerSuccwss 购买成功切验证成功
                                   }
                               }
                           }];
    
    // 验证成功与否都注销交易,否则会出现虚假凭证信息一直验证不通过,每次进程序都得输入苹果账号
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    
}

PS:验证流程

订单正确性的验证:
1.iOS客户端(购买成功)→ 到苹果服务器验证 → 苹果服务器返回验证结果,做相应处理
2.iOS客户端(购买成功)→ 后台 → 苹果服务器验证 → 苹果服务器返回验证结果,做相应处理

服务器要做的是:

1.接收iOS前端发过来的购买凭证。

2.判断凭证是否已经存在或验证过,然后存储该凭证。

3.将该凭证发送到对应环境下的苹果服务器验证,并将验证结果返回给客户端。

4.根据需求,是否修改用户相应信息。

注意事项

1.bundleID要与iTunes Connect上你App的相同,不然是请求不到产品信息的

2.在沙盒环境进行测试内购的时候,要使用没有越狱的苹果手机。

3.在沙盒环境下真机测试内购时,请去app store中注销你的apple ID,不然发起支付购买请求后会直接case:SKPaymentTransactionStateFailed。使用沙盒测试员的账号时不需要真正花钱的。

4.如果只添加了一个沙盒测试员账号,当一个真机已经使用了这个账号,另一个真机再使用这个账号支付也是会发生错误的。那就去多建几个沙盒测试员账号使用不同的,反正也是免费的,填写也很快。

5.监听购买结果,当失败和成功时代码中要调用:

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

该方法通知苹果支付队列该交易已完成,不然就会已发起相同 ID 的商品购买就会有此项目将免费恢复的提示。

请在本地做一下凭证存储

现在订单正确性的验证是:iOS客户端(购买成功)→ 后台→后台到苹果服务器验证→处理后台返回结果做相应逻辑处理。

注意:
如果Your App 和 Your Server 中断了链接
当我们前端购买成功后,凭证(receipt)本地保留一份,当与后台验证成功后,再将本地保留的凭证删除。
否者一直使用本地已经保留的凭证与后台交互。

测试前提条件:
1.在itunesConnect中填写测试账号。
2.在itunesConnect中填写税务单(就是银行账号,开户名,收款机构等等的税务单)。
3.交易收据内容(receipt)

 "receipt":
    {
        "original_purchase_date_pst":"2015-06-22 20:56:34 America/Los_Angeles", //购买时间,太平洋标准时间
        "purchase_date_ms":"1435031794826", //购买时间毫秒
        "unique_identifier":"5bcc5503dbcc886d10d09bef079dc9ab08ac11bb",//唯一标识符
        "original_transaction_id":"1000000160390314", //原始交易ID
        "bvrs":"1.0",//iPhone程序的版本号
        "transaction_id":"1000000160390314", //交易的标识
        "quantity":"1", //购买商品的数量
        "unique_vendor_identifier":"AEEC55C0-FA41-426A-B9FC-324128342652", //开发商交易ID
        "item_id":"1008526677",//App Store用来标识程序的字符串
        "product_id":"cosmosbox.strikehero.gems60",//商品的标识 
        "purchase_date":"2015-06-23 03:56:34 Etc/GMT",//购买时间
        "original_purchase_date":"2015-06-23 03:56:34 Etc/GMT", //原始购买时间
        "purchase_date_pst":"2015-06-22 20:56:34 America/Los_Angeles",//太平洋标准时间
        "bid":"com.cosmosbox.StrikeHero",//iPhone程序的bundle标识
        "original_purchase_date_ms":"1435031794826"//毫秒
 }

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

推荐阅读更多精彩内容

  • 花了快10个工作日,终于完成了内购(IAP)功能。必须写篇文章来记录一下这十天来的心得体会,更是为了避免后续的开发...
    flightlessBirdT阅读 9,194评论 16 38
  • iOS应用如果涉及到支付功能,分为两类:第三方支付和苹果内购。那么什么情况下选择使用第三方支付,又在什么情况下选择...
    ZfRee阅读 38,316评论 36 66
  • 参考借鉴了一下几篇文章(在这里很感谢各位作者大大做出的总结和方案): http://blog.csdn.net/a...
    辉546阅读 13,578评论 3 12
  • 自己开发的视频直播项目,牵涉到充值金币,用到了苹果公司的内购,趴坑了两天,这里总结下实现苹果内购。 一. 创建测试...
    Leo丶Dicaprio阅读 3,247评论 8 7
  • Web前端框架就是为了节约开发成本和时间,一般开发一个项目都会用到前端框架(除非自己有前端开发团队),根据我经验找...
    UIleader阅读 3,567评论 0 4