美文网首页iOS程序员的业余沙龙demo小知识点
iOS内购—— In-App Purchase(消耗型)

iOS内购—— In-App Purchase(消耗型)

作者: ZfRee | 来源:发表于2016-12-08 14:45 被阅读13942次

    iOS应用如果涉及到支付功能,分为两类:第三方支付和苹果内购。那么什么情况下选择使用第三方支付,又在什么情况下选择苹果内购呢?让我们先来简单了解一下:

    Understanding What You Can Sell Using In-App Purchase

    You can use In-App Purchase to sell content, app functionality, and services.

    App functionality.Unlock behavior and expand features you’ve already delivered. Examples include a free game that offers multiplayer mode as an in-app purchase and a free weather app that lets users make a one-time purchase to remove ads.

    Services.Have users pay for one-time services such as voice transcription and for ongoing services such as access to a collection of data.

    You can’t use In-App Purchase to sell real-world goods and services or to sell unsuitable content.

    Real-world goods and services.You must deliver a digital good or service within your app when using In-App Purchase. Use a different payment mechanism to let your users buy real-world goods and services in your app, such as a credit card or payment service.

    Unsuitable content.Don’t use In-App Purchase to sell content that the isn’t allowed by the App Review Guidelines—for example, pornography, hate speech, or defamation.

    开发文档的这里:Understanding What You Can Sell Using In-App Purchase

    开发者中心的这里:https://developer.apple.com/app-store/review/guidelines/#payments

    ```

    购买的内容如果要在 app 内部使用,必须使用 IAP ,但是你要买的东西与 app 本身无关,就不能使用 IAP 。For Example:你用淘宝、京东的 app 买个鼠标,真实物品就不能使用IAP。但是要想在斗鱼app内买虚拟物品来送主播礼物,则必须使用 IAP ,不走内购就不行。

    ```


    一、先到iTunes Connect上填写协议、税务和银行业务

    如果你是外包公司,那么你可以让你的客户填写这一堆信息;如果你只是是产品公司的技术开发人员,那么你可以让项目负责人填写这一堆信息;如果没有如果,兄弟辛苦了,自己动手来吧。

    第一步 第二步 第三步 第四步 第五步

    先点击Contact Info 的Set Up

    第六步 第七步 第八步 第九步 第十步 第十一步 第十二步

    进行十二步的时候可能有些银行通过下面的Look up CNAPS Code方法查不到,就需要借助百度了,一定要准确查询,否则会有问题。推荐一个地址

    https://e.czbank.com/CORPORBANK/query_unionBank_index.jsp

    第十三步

    这一步需要注意的是,货币类型可能有歧义,看你是想收美元还是人民币了,都说美元合适。不过,我做的时候为了避免事情,还是选择了CNY,支持国产。还有一点,银行账号如果是对公的账号,需要填写公司的英文名称,如果没有的话,上拼音!然后点击保存银行信息就算ok了,然后退回到最开始的页面

    第十四步 第十五步 第十六步 第十七步 第十八步 第十九步 第二十步 第二十一步(完成)


    二、为app添加内购产品

    在iTunes Connect在你要添加内购的app中,进入到功能页面

    添加内购项目

    在你点击添加内购产品按钮后会有弹框,提示你选择类型,这个就要看你app的需求了

    选择类型 名称和定价 本地化版本(可以“+”添加语言) 填写审核信息(现在的图)

    填写完审核信息后,点击右上角的“存储”按钮,就添加了一个内购产品~


    三、添加沙盒技术测试员

    在iTunes Connect的用户和智能中选择“沙盒技术测试员”,填写信息保存以后就有一个测试员了

    添加沙盒测试员


    四、代码部分

    首先是.h文件

    #import <UIKit/UIKit.h>

    #import <StoreKit/StoreKit.h>

    #import "MBProgressHUD.h"

    @interface GiftChartViewController :UIViewController<SKPaymentTransactionObserver,SKProductsRequestDelegate>

    @end

    然后是.m文件

    //下面的字符串就是你在iTunes Connect上的内购项目的产品ID,

    static const NSString *productCoin60 =@"随便写的60";

    static const NSString *productCoin300 =@"随便写的300";

    static const NSString *productCoin600 =@"随便写的600";

    static const NSString *productCoin980 =@"随便写的980";

    static const NSString *productCoin1280 =@"随便写的1280";

    static const NSString *productCoin2580 =@"随便写的2580";

    typedefNS_ENUM(NSInteger,coinType) {

    coinType60,/** 60金币*/

    coinType300,/** 300金币*/

    coinType600,/** 600金币*/

    coinType980,/** 980金币*/

    coinType1280,/** 1280金币*/

    coinType2580/** 2580金币*/

    };

    #pragma mark - lifecycle

    - (void)viewDidLoad {

    [super viewDidLoad];

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    }

    //支付的点击事件

    - (void)payAction:(id)sender{

    NSLog(@"pay pay pay");

    NSLog(@"-------------请求对应的产品信息----------------");

    if([SKPaymentQueue canMakePayments]) {

    [self requestProductData];

    }else{

    //做一些提示

    }

    }

    - (void)requestProductData{

    NSArray *product;

    switch(_buyCoinType) {

    case:coinType60:

    product =@[productCoin60];

    break;

    case:coinType300:

    product =@[productCoin300];

    break;

    case:coinType600:

    product =@[productCoin600];

    break;

    case:coinType980:

    product =@[productCoin980];

    break;

    case:coinType1280:

    product =@[productCoin1280];

    break;

    case:coinType2580:

    product =@[productCoin2580];

    break;

    default:

    break;

    }

    NSLog(@"请求的产品%@",product);

    NSSet *nsset = [NSSet setWithArray:product];

    SKProductsRequest *request = [[SKProductsRequest alloc]initWithProductIdentifiers:nsset];

    request.delegate =self;

    [request start];

    [MBProgressHUD showHUDAddedTo:self.view animated:YES];

    }

    #pragma mark - SKProductsRequestDelegate

    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{

    NSLog(@"-----------收到产品反馈信息--------------");

    NSArray *myProduct = response.products;

    NSLog(@"产品Product ID:%@",response.invalidProductIdentifiers);

    NSLog(@"产品付费数量: %d", (int)[myProduct count]);

    // populate UI

    for(SKProduct *productinmyProduct){

    NSLog(@"product info");

    NSLog(@"SKProduct描述信息%@", [product description]);

    NSLog(@"产品标题%@", product.localizedTitle);

    NSLog(@"产品描述信息: %@", product.localizedDescription);

    NSLog(@"价格: %@", product.price);

    NSLog(@"Product id: %@", product.productIdentifier);

    [MBProgressHUD hideHUDForView:self.view animated:YES];

    SKPayment *payment = [SKPayment paymentWithProduct:product];

    [[SKPaymentQueue defaultQueue] addPayment:payment];

    //addPayment 将支付信息添加进苹果的支付队列后,苹果会自动完成后续的购买请求,在用户购买成功或者点击取消购买的选项后回调

    }

    //payment = [SKPayment paymentWithProductIdentifier:coin60];这个方法不要使用了

    //+ (id)paymentWithProductIdentifier:(NSString*)identifier NS_DEPRECATED_IOS(3_0,5_0,"Use +paymentWithProduct: after fetching the available products using SKProductsRequest");

    };

    - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{

    NSLog(@"------------------错误-----------------:%@", error);

    }

    - (void)requestDidFinish:(SKRequest *)request{

    NSLog(@"------------反馈信息结束-----------------");

    }

    //监听购买结果的回调

    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{

    for(SKPaymentTransaction *tranintransaction){

    switch(tran.transactionState) {

    case:SKPaymentTransactionStatePurchased:{

    NSLog(@"交易完成");

    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

    [self verifyTransactionResult];

    break;

     }

    case:SKPaymentTransactionStatePurchasing:

    NSLog(@"商品添加进列表");

    break;

    case:SKPaymentTransactionStateRestored:

    NSLog(@"已经购买过商品");

    break;

    case:SKPaymentTransactionStateFailed:{

    NSLog(@"交易失败");

    [MyTaShowMessageView showMessage:@"交易失败!"];

    [[SKPaymentQueue defaultQueue] finishTransaction:tran];

    break;

    }

    default:

    break;

    }

    }

    }

    - (void)dealloc{

    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

    }

    购买成功后我们iOS前端可以单独在客户端完成订单正确性的验证。但是因为有的项目后台要Android和iOS两端生成账单便于对账。所以我们请求后台接口,服务器处验证是否支付成功,依据后台返回结果做相应逻辑处理。

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

    服务器要做的是:

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

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

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

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

    官方文档应该也是支持的这么做的→In-App Purchase Programming Guide

    - (void)verifyTransactionResult{

    //验证凭据,获取到苹果返回的交易凭据

    // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址

    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];

    //从沙盒中获取到购买凭据

    NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];

    //传输的是BASE64编码的字符串

    /**

    BASE64常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性,BASE64是可以编码和解码的。

    */

    NSDictionary *requestDict =@{@"receipt-data": [receipt base64EncodedStringWithOptions:0],@"sandbox":@"1"};

    /**

    请求后台接口,服务器处验证是否支付成功,依据返回结果做相应逻辑处理

    与后台协调好,让后台根据你的“sandbox”字段的1,0来区分请求是正式环境还是测试环境

    (当然“sandbox”这个字段也可以替换为你想要的,但是“receipt-data”不能替换,要注意!)

    详情点这里→苹果官方文档

    */

    //请求成功的response自己输出看一下吧,status是0就成功了,这里就不贴出来了,因为有一些敏感数据,比如你的bundleID,product_id之类的

    }

    下面是两种环境下的苹果服务器验证地址

    // Create a POST request with the receipt data.

    //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt

    //In the real environment, use https://buy.itunes.apple.com/verifyReceipt

    到这里最基本的内购流程就可以跑通了~

    五、要注意的事项!

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

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

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

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

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

    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

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


    六、请在本地做一下凭证存储!

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

    头疼的情况

    针对图上的情况:当我们前端购买成功后,凭证本地保留一份,当与后台验证成功后,再将本地保留的凭证删除。否者一直使用本地已经保留的凭证与后台交互。


    七、最后

    在第一节 :先到iTunes Connect上填写协议、税务和银行业务中,因为我自己的开发者账号已经填写过一遍这个信息了,步骤无法复现。所以在征得简书作者:睡不着的叶-《iOS开发 内购流程 手把手教你还不学?》的同意后,转用了部分图片,在此表示感谢。


    写的第一篇文章,写的不好的地方,大家多多包涵,在作为iOS开发者的路上我们一起奔跑~

    相关文章

      网友评论

      • 柏林日记:有个问题,在做app国际化的时候,要实现一键切换语言时,也显示相应币种的价格,如何实现呢?商品信息貌似只能请求本地区的币种
      • 卓敦:你好,为什么你定义那么多产品ID呢,购买的金额不能用户自己自定义的吗,新手求解
        卓敦:@ZfRee 还有非消耗的想请教一下,我APP有很多杂志,怎么创建产品id,他们的产品id好像和杂志是一一对应的,难道我要创建很多个产品id吗
        ZfRee:@卓敦 产品ID和金额 需要先在网站上定义好,并且UI展示的金额和定义的一致,不能在app运行的时候进行自定义
      • e2f2d779c022:写的很棒, 我写了一套完整的 IAP 方案, 处理了 IAP 的九大坑,感兴趣的朋友来看看吧:

        第一篇:[[iOS]贝聊 IAP 实战之满地是坑](https://www.jianshu.com/p/07b5ec193353),这一篇是支付基础知识的讲解,主要会详细介绍 IAP,同时也会对比支付宝和微信支付,从而引出 IAP 的坑和注意点。

        第二篇:[[iOS]贝聊 IAP 实战之见坑填坑](https://www.jianshu.com/p/8e5bf711f9f0),这一篇是高潮性的一篇,主要针对第一篇文章中分析出的 IAP 的问题进行具体解决。

        第三篇:[[iOS]贝聊 IAP 实战之订单绑定](https://www.jianshu.com/p/847838cde48b),这一篇是关键性的一篇,主要讲述作者探索将自己服务器生成的订单号绑定到 IAP 上的过程。
      • 先疯:“This In-App Purchase has already been bought. It will be restored for free.”
        你们有遇到过吗?老板想解决掉续费提示的这个问题
      • 89c90c396360:很有帮助
      • 香了个蕉111:到此一游 FreeFei
      • 骑马纵天下:楼主在吗?
      • imChay:您好,有几个问题想请教一下:
        1、审核信息的图片没有或者不想传怎么办?
        2、配置的银行卡需不需要开通国际支付功能呢?
        3、关于上面 “六、请在本地做一下凭证存储!”,凭证存储在本地,等网络恢复了还要再向服务器发起请求直到成功为止是吗
        ZfRee:@imChay app截图就好了,为什么会没有图片;提示的时候是你把凭证交付给后台失败时,在不成功的情况下后台和app前端的金币数量都不会有变化的
        imChay:@ZfRee 没有相关图片,图片任意传可以吗;如果只提示“再次购买此商品不需要付款”之类的,后台还是没有记录用户的充值数量,当用户使用金币的时候是不是就会出现问题
        ZfRee:1.这个图片是需要传的;
        2.我当时用的是VISA的卡,应该银联的卡是需要开通的,因为苹果是有内购退款的;
        3.用户调起内购进行购买后钱被扣除,但是因为网络原因和后台中断访问连接,这时给他一个提示,例如“再次购买此商品不需要付款”之类的,当他再次买这件商品时,判断本地是否有这个商品的凭证,有的话 直接和后台交互(跳过调起内购)。
      • jacinzhang:你好,我想问下,我这边配置了 product, 但是 product 状态始终是 准备提交,product 详情页 main 提交以供审核按钮也是无法点击的,不是已批准状态,我代码获取到的 product id 都是不可用的,无法获取到可用的product,
      • 834a516c154f:我们生成的订单怎么对应上这个订单呢
        奥卡姆剃须刀:这个应该是在后台对应的把
      • a2ebcc3676c1:流程很全,Mark
      • pinglife:如果我要是年会员或者月会员的话选择是哪个呀?是自动续订订阅吗?
        ZfRee:你也可以把它当成消耗型的商品,我记得当时试验的时候,订阅和消耗型的返回数据是有区别的
      • 啦啦啦啦啊:你好,按照您的步骤实现,为什么在登录测试账号的时候 密码输对的情况下提示:验证您的APPID 以继续, 然后验证了 就提示 账号不存在,或者密码输入错误呢??
        ZfRee:@啦啦啦啦啊 测试环境下 这个地址https://sandbox.itunes.apple.com/verifyReceipt
        啦啦啦啦啊:@ZfRee 已经退出了。还是不行。
        ZfRee:先到手机的app store上 退登真实账号 然后登录测试账号
      • mysteryemm:税务银行等信息填写完毕,为什么我的装态一直是Processing ,没有生效?
        ZfRee:需要审核,也可能是外国新年期间工作效率也低呢。
      • 超_iOS:You may have already accidentally created another account using that info, and certain information has to be unique to each new account. The only way to clear this up is to contact Apple. 这个提示是因为什么?请求合同时出来的.难道同一个人只能填写一次?
      • 最后还是个农:您好,发现有一个地方您没有提到,请问下:在第十八步后还有一个:Part I: Identification of Beneficial Owner 没有看您提到,不知道如何选择,
      • 上善若水jf:不错👌
        ZfRee:@上善若水jf 谢啦 :grin:

      本文标题:iOS内购—— In-App Purchase(消耗型)

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