iOS 内购最新版(埋坑)

作者: 奥卡姆剃须刀 | 来源:发表于2017-04-04 23:50 被阅读1262次

    最近项目中要加进去虚拟币和会员的功能 本以为特别简单不就是几十行代码嘛 但是内购做下来 可把我坑惨了 有什么问题尽可留言 一定帮你们顺利通过内购

    文章末尾有内购遇见的bug 以及解决办法

    一 首先区分一下 Apple Pay 和内购完全是两码事

    本来说集成内购的 但我们后台大哥说可以使用apple pay 也是苹果的支付系统 没做过内购我以为就是一样的 然后和我们超哥 三下五除二的把applepay集成进来的 然后也是线下测试 线上测试 一大堆 终于好了 开始提测 果然 因为applepay悲剧了 呵呵呵呵了

    二 开始集成内购

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

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


    Paste_Image.png Paste_Image.png

    先点击Contact Info 的Set Up

    Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png

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

    Paste_Image.png

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

    Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png

    2 为app添加内购产品

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

    Paste_Image.png

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

    Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png

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

    3添加沙盒技术测试员

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

    Paste_Image.png

    直接鲁代码

    #import <StoreKit/StoreKit.h>
    
    @interface LLMIneCoinRechargeController ()<UIAlertViewDelegate,SKProductsRequestDelegate,SKPaymentTransactionObserver>
    
    @property(nonatomic, strong)NSArray<LLMIneCoinRechargeModel *> *coinModelArr;
    
    @property(nonatomic,copy)NSString * bvip;
    
    @property(nonatomic,copy)NSString * coin;
    
    @property(nonatomic, strong)SKProductsRequest *request;
    
    @property(nonatomic,copy)NSString * productID;
    @implementation LLMIneCoinRechargeController{
        
        UIAlertView* _alertView;
        NSString *coinStr;
    }
    
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //一定要 开启内购检测
        [[SKPaymentQueue defaultQueue]addTransactionObserver:self];
    
    }
    
    // 在点击事件中去添加代码
    - (void)payBtnClick:(UIButton *)sender{
        
        __block  LLMIneCoinRechargeModel *model = nil;
        
        [self.coinModelArr enumerateObjectsUsingBlock:^(LLMIneCoinRechargeModel * obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if (obj.selected) {
                model = obj;
            }
        }];
        if (!model) {
            [HudManager showMessage:@"请选择金额"];
            return;
        }
        
        NSString *proID = [NSString stringWithFormat:@"charge%@",model.coinID];
        
        WS(weakSelf);
        if ([SKPaymentQueue canMakePayments]) {
            [weakSelf requestProductData:proID];
            weakSelf.productID = proID;
            
        }else{
            [HudManager showMessage:@"不允许程序内付费"];
        
        }
    }
    
    // 收到请求信息
    - (void)requestProductData:(NSString *)productID{
    
        NSLog(@"-------------请求对应的产品信息----------------");
        
        [SVProgressHUD showWithStatus:nil maskType:SVProgressHUDMaskTypeBlack];
        
        NSArray *product = [[NSArray alloc] initWithObjects:productID,nil];
        
        NSSet *nsset = [NSSet setWithArray:product];
        _request = [[SKProductsRequest alloc]initWithProductIdentifiers:nsset];
        
        _request.delegate = self;
        
        [_request start];
    }
    
    
    // 收到返回信息
    
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    
        NSArray *product = response.products;
        
        if (product.count == 0) {
            
            [SVProgressHUD dismiss];
            [HudManager showMessage:@"购买失败"];
            return;
        }
        
        SKProduct *prod = nil;
        for (SKProduct *pro in product) {
            
            if ([pro.productIdentifier isEqualToString:self.productID]) {
                prod = pro;
            }
        }
        
        // 发送购买请求
        if (prod != nil) {
            SKPayment *payment = [SKPayment paymentWithProduct:prod];
            [[SKPaymentQueue defaultQueue]addPayment:payment];
        }
    }
    
    // 失败回调
    - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
        
        [SVProgressHUD dismiss];
        [HudManager showMessage:@"购买失败"];
        
    }
    
    // 支付后的反馈信息
    - (void)requestDidFinish:(SKRequest *)request{
    
        [SVProgressHUD dismiss];
        
    }
    
    // 监听购买结果
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
        
        for (SKPaymentTransaction *tran in transactions) {
            
            switch (tran.transactionState) {
                    
                case SKPaymentTransactionStatePurchased:
                    
                    [self verifyPurchaseWithPaymentTransactionWith:tran];
                    
                    break;
                case SKPaymentTransactionStatePurchasing:
                    NSLog(@"商品已经添加进列表");
                    break;
                    
                case SKPaymentTransactionStateRestored:
                    NSLog(@"已经购买过商品");
                    
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                    
                    break;
                case SKPaymentTransactionStateFailed:
                    
                    NSLog(@"购买失败");
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                    [HudManager showMessage:@"购买失败"];
                    
                    break;
                default:
                    break;
            }
        }
    }
    
    /**
     *  验证购买,避免越狱软件模拟苹果请求达到非法购买问题
     *
     */
    -(void)verifyPurchaseWithPaymentTransactionWith:(SKPaymentTransaction *)tran{
        
        //从沙盒中获取交易凭证并且拼接成请求体数据
        NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL];
        NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl];
    
        NSString  *receiptString = [receiptData base64EncodedStringWithOptions:0];
        
        if (!receiptString) {
            return;
        }
    
    // 将base64 编码 发给后台服务器 做验证 以及是否是沙箱环境的参数
        
        NSDictionary *dict = [NSDictionary universalParameteWithDictionary:@{@"receipt":receiptString,
                                                                             @"sandbox":BOOLSandBox,
                                                                             }];
        
        [[LFBHTTPSessionManager manager]POSTwithURLString:KApplePay_Docheck parameters:dict success:^(id data) {
            
            [self loadData];
            [[SKPaymentQueue defaultQueue] finishTransaction:tran];
            
            
        } failed:^(NSError *error) {
    //        [HudManager showMessage:error.description];
        }];
       
    }
    
    
    - (void)dealloc{
        
        [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
        
    }
    
    
    

    代码基本上就上边的了

    四 、要注意的事项!

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

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

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

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

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

    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

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

    1

    uideline 3.1.1 - Business
    We noticed that your app enables the purchase of content, services, or functionality in the app by means other than the in-app purchase API, which is not appropriate for the App Store.

    解决办法 这个被拒的原因很简单了 是要要使用内购了

    2 切记 内购和Apple Pay 是两回事
    3

    We noticed that your in-app purchase product is set to an incorrect product type.

    VIP is set to consumable.

    Next Steps

    Since the service offered by your app requires the user to make an advance payment to access the content or receive the service, please use the non-renewable subscription in-app purchase product type. Non-renewable subscription content must be made available to all iOS devices owned by a single user, as indicated in guideline 3.1.2 of the App Store Review Guidelines.

    Note: The product type cannot be changed once an in-app purchase product has been created. Therefore, you will need to create a new in-app purchase product with the correct product type.

    解决办法 : 虚拟币是消耗类型 但是一定要记住 会员VIP不是消耗类型的的 是非订阅类型的 更改你的VIP类型

    4

    Guideline 3.1.2 - Business

    Your app offers a content subscription but does not have a mechanism in place to support the requirement that the subscription content be available to the user on all of their iOS devices.

    Next Steps

    To resolve this issue, please modify your app to include an optional user registration feature to deliver subscription content to all of the user's iOS devices. Such user registration must be made optional, not required. We also recommend indicating that registering is required to access the subscription content from their other iOS devices - and providing a way to register later, if users wish to have access to this content at a future time.

    这个被拒的原因就感觉比较奇葩了 不知道是不是因为vip是费续期订阅的问题 因为我的项目中有游客入口 也就是不用登陆 也可以进去看到部分内容以及使用部分功能 : 所以应该是这个游客入口 和内购的一些规则冲突了
    有两种解决办法:

    1 就是游客模式 不能出现购买虚拟币以及购买VIP的页面 (我是这么改的 然后重新提交审核 就通过了)
    2 有朋友是这么做的 也通过审核了 但个人感觉相对麻烦一点

    就是 游客模式可以出现购买虚拟币以及购买VIP的页面 而且允许游客购买 购买后的凭证要保存在本地 然后当用户登录的时候 把这个凭证在发给服务器去验证 验证成功后 将虚拟币或者是VIp加到当前账户上

    相关文章

      网友评论

      • 瓜而不皮很灵性:请问一下,内购可以使用ApplePay的方式吗?
      • 3fdb581ec682:大神,请教一下,那个内购的话,被苹果拒绝3了,必须的加游客模式,但是我们的项目做的是账号登陆后购买付费资源,自己有一个虚拟账户,通过内购充值,怎么可以避开游客模式
      • 落日笔记本:大神,我想问一下 ,我app中有作品,作品只有会员才能购买,我通过非订阅的购买了会员,可以看作品了,购买作品使用的是内购的虚拟货币,这样会被苹果拒下来吗,只有会员的情况下,才能用虚拟货币购买作品
        落日笔记本:@奥卡姆剃须刀 2822966146加个QQ聊聊
        落日笔记本:@奥卡姆剃须刀 我作品查看是需要会员权限的,没会员的话,就提示作品查看为VIP会员内容,请加入VIP,苹果去开通会员之后,进去查看作品,购买作品,然后,我就被他给拒回来了,理由:具体地说,我们还需要支付作品在我们购买高级设计通会员。
        奥卡姆剃须刀:应该没问题的 要注意在非会员的情况下: 是否显示作品的购买按钮以及点击购买作品的提示
      • c805e47961b8:楼主。我的app在未登录的状态下不显示充值会员与充值虚拟币。但是提供了测试账号。结果被要求游客状态下也要可以购买内购。咋办
        奥卡姆剃须刀:@DNotCoder 未登陆状态下只要有虚拟币的页面都不能显示的:joy:
      • XiaoL_coder:“1 就是游客模式 不能出现购买虚拟币以及购买VIP的页面 (我是这么改的 然后重新提交审核 就通过了)”,
        楼主,你最近按这种方式更新含有内购的app了吗?这种方式现在会不会被拒
        奥卡姆剃须刀:@XiaoL_coder 我现在一直就是各种方式,没被拒,
      • 双子座的小小杂货铺:你好,我已经添加了沙盒测试账号,现在错误的原因是“无法连接到iTunes Store”,添加的内购项目提示“元数据丢失”,沙盒上号登录app store时提示“不允许创建iTunes store--此Apple ID目前无妨用于iTunes Store”,这个怎么解决啊,谢啦
        奥卡姆剃须刀:@我是双子__所以呢 客气了 互相学习进步
        双子座的小小杂货铺:@奥卡姆剃须刀 恩,谢啦,我再看看:+1:
        奥卡姆剃须刀:这个沙河测试账号不能直接登录app store 需要在你app内点击内购的时候 弹框 在登录你的测试账号 至于 无法连接到iTunes Store 这个就是苹果的问题或者 网的问题等
      • LD_左岸:大神您好,您的第二张图--->我点了Request之后 弹窗提示我:Add New Legal Entity
        Address line 1 cannot be more than 40 characters


        这个是什么意思
        Address Line 1 这一行 这不报超过40个字符了 而且这一行里填写的地址 也是自动生成的 应该和我那个申请证书地址一样 我现在把这一行的东西 分成两部分 在Address Line 1这一行里填 城市名 在Address Line 2这一行里填具体的街道 是否可行
        奥卡姆剃须刀:@左岸__ 这个对公账号的名字和我们开发者账号的名字 我们恰好是一样的 至于不一样会不会又所影响 这个我还真不清楚 。 抱歉哈
        LD_左岸:@奥卡姆剃须刀 银行账户我填的对公账户 必须是开通外汇功能的 第二 我填的对公账户的开户人必须是英文的(苹果后台输不了中文) 第三只要满足以上两点就行了 还是说 这个开户人还必须和苹果开发者账户的名称保持一致, 比如99$的公司账号是BJDaShenTechology CO.,LTD那么这个对公账户的开户名 也得是这个BJDaShenTechology CO.,LTD
        比如99$的个人账号是shunzi Wang那么这个对公账户的开户名 也得是这个shunzi Wang
        奥卡姆剃须刀:可以的 其实这个并不是很重要,只要把银行卡信息那部分认真填好就好
      • 骑马纵天下:楼主在吗?
      • 我用香蕉跟你换:楼主,我们项目有二百多种价格,那我得创建二百多个产品id吗
        奥卡姆剃须刀:@向着明天出发 关于会员的产品都是非订阅类型的,一路被拒过来的:sweat:
        fbb98a20163b:QQ会员不应该也是消耗性的吗?非订阅类型的再次购买会提示已经购买过了呀,提示感觉不太好
        奥卡姆剃须刀:@我用香蕉跟你换 据我所知,是的,每一个产品对应一个id 😀
      • 丛林歌者_22836:楼主 内购产品必须审核过才能测试吗?
        奥卡姆剃须刀:@丛林歌者_22836 是的😀
        丛林歌者_22836:就是说按照您这篇文章描述的做就可以测试了 不需要其它的步骤 谢谢楼主
        奥卡姆剃须刀:内购产品添加成功后 沙箱环境就可以测试了 审核通过是指的是线上的 我记得是这样的
      • 37deec65fd71:楼主,VIP不能用消耗型的内购金币去购买吗?如果改成非续订订阅类型的是不是要重新做界面,不用充值金币的界面来先购买金币再购买VIP?我的VIP价格是不固定的,而内购项目里面是固定这就没法做了啊。
        37deec65fd71:@努力的iOSDeveloper 我看到有的APP里面的Vip是通过消耗型的内购后的金币购买的,当金币不够显示的是前往充值金币页面的,我们公司是看了他们有的才觉得行的.
        37deec65fd71:@努力的iOSDeveloper 嗯 这搞得蛮不方便的哦,本来就是内购充了金币不能用来购买, 那就只能重新创建固定的的Vip等级价格列表了对吗?
        奥卡姆剃须刀:vip只能是非续订订阅类型的 本来我们的VIp价格也是不固定 后来只能按照苹果的走了 哎
      • 拉风的胖鱼:请问下,我用沙盒测试内购,为什么最后会提示你没权限购买InApp?
        奥卡姆剃须刀:@farewellyw :joy:
        拉风的胖鱼:@努力的iOSDeveloper 知道原因了,是我没把这些商品ID加进去
        奥卡姆剃须刀:这个我暂时还没有遇见过 不好意思
      • 行走的bugstyle:VIP不是消耗型???是非续期类型????
        奥卡姆剃须刀:@彳亍在心田 你可以在看看我新加的第四条被拒的原因 或许对你有帮助
        行走的bugstyle:@liuniuliuniuliu 十分感谢啊,最近也在弄VIP的会员,我看网上都写得是消耗型。幸好看到你的改过来了
        奥卡姆剃须刀:是的 我也是一路坑过来的

      本文标题:iOS 内购最新版(埋坑)

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