iOS Apple内购及掉单问题

作者: 走在路上的小菜鸟 | 来源:发表于2016-04-27 15:06 被阅读16143次

    在iOS开发中你是否遇到过购买虚拟物品的而无法使用第三方支付的问题,让不熟悉Apple内购的你不知所措,废话不多说,直接搞起。

    第一部分:协议


    第1步.png 第2步.jpg
    第3步.jpg
    第4步.png
    第5步.png
    第6步.jpg
    第7步.png
    第8步.jpg
    第9步.jpg
    第10步.png

    CNAPS CODE 查询地址
    https://e.czbank.com/CORPORBANK/query_unionBank_index.jsp

    第11步.jpg 第12步.jpg 第13步.png
    第14步.png
    第15步.jpg
    ![第16步.jpg](http:https://img.haomeiwen.com/i1975881/dd1ac4d3d8fdd555.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    第17步.png
    第18步.png

    协议Done,我们现在已经和Apple签订了协议,接下来该去上架商品了

    第二部分:创建内购项目

    第1步.png
    第2步.png
    第3步.png
    第4步.png
    第5步.png
    第6步.png
    第7步.png

    Apple内购的价格是等级制的,无法自己随意定价,而且每比订单成交都要向苹果缴纳百分之30的抽成,坑爹吧!!

    第三部分:App代码集成

    介绍一下APP内购的步骤:

    一般的内购分为两种,一种是我们app有自己的服务器,一种是本地的,像我们玩的闯关游戏需要购买关卡一般都是本地的,像那种联网手游,会员VIP的基本都是服务器的。

    服务器模式:
    1.调用服务器接口创建一个商品的订单
    2.请求Apple的商品列表
    3.选取商品调用苹果支付
    4.支付成功(会返回凭证)
    5.把支付成功的返回凭证上传到APP服务器(带上订单的ID,有利于后台判断是哪个订单支付成功)
    6.APP服务器保存该凭证等数据并像苹果服务器发起凭证验证,验证成功则发送商品

    本地模式:
    1.请求Apple的商品列表
    2.选取商品调用苹果支付
    3.支付成功(会返回凭证)
    4.把凭证与商品发送状态保存到一个本地的数据库
    5.app调用apple服务器的验证API
    6.验证成功发送商品并改变数据库的物品发送状态

    最后一步了,是不是有些欣喜,最后在代码中实现

    首先导入StoreKit.framework库
    
    #import "ApplePayVC.h"
    #import <StoreKit/StoreKit.h>
    
    //在内购项目中创的商品单号,从itunesConnect里可以看到
    #define ProductID_1 @"product1"
    #define ProductID_2 @"product2" 
    #define ProductID_3 @"product3" 
    #define ProductID_4 @"product4"
    #define ProductID_5 @"product5"
    
    @interface ApplePayVC ()
    {
        NSString *buyProductId;
    }
    @end
    
    @implementation ApplePayVC
    
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        [self buyProduct:ProductID_1];
        
    }
    
    -(void)buyProduct:(NSString *)productId
    {
        buyProductId = productId;
        if ([SKPaymentQueue canMakePayments]) {
            [self RequestProductData];
            NSLog(@"允许程序内付费购买");
        }
        else
        {
            NSLog(@"不允许程序内付费购买");
            UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@"提示"
                                                                message:@"您的手机没有打开程序内付费购买"
                                                               delegate:nil cancelButtonTitle:NSLocalizedString(@"关闭",nil) otherButtonTitles:nil];
            
            [alerView show];
            
        }
    }
    
    -(void)RequestProductData
    {
        NSLog(@"---------请求对应的产品信息------------");
        NSArray *product = [[NSArray alloc] initWithObjects:buyProductId,nil];
        NSSet *nsset = [NSSet setWithArray:product];
        SKProductsRequest *request=[[SKProductsRequest alloc] initWithProductIdentifiers: nsset];
        request.delegate=self;
        [request start];
        
    }
    
    //<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 *product in myProduct){
            NSLog(@"product info");
            NSLog(@"SKProduct 描述信息%@", [product description]);
            NSLog(@"产品标题 %@" , product.localizedTitle);
            NSLog(@"产品描述信息: %@" , product.localizedDescription);
            NSLog(@"价格: %@" , product.price);
            NSLog(@"Product id: %@" , product.productIdentifier);
        }
        SKPayment *payment = [SKPayment paymentWithProductIdentifier:buyProductId];
     
        NSLog(@"---------发送购买请求------------");
        [[SKPaymentQueue defaultQueue] addPayment:payment];
        
    }
    - (void)requestProUpgradeProductData
    {
        NSLog(@"------请求升级数据---------");
        NSSet *productIdentifiers = [NSSet setWithObject:@"com.productid"];
        SKProductsRequest* productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
        productsRequest.delegate = self;
        [productsRequest start];
        
    }
    //弹出错误信息
    - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
        NSLog(@"-------弹出错误信息----------");
        UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Alert",NULL) message:[error localizedDescription]
                                                           delegate:nil cancelButtonTitle:NSLocalizedString(@"Close",nil) otherButtonTitles:nil];
        [alerView show];
        
    }
    
    -(void) requestDidFinish:(SKRequest *)request
    {
        NSLog(@"----------反馈信息结束--------------");
        
    }
    
    -(void) PurchasedTransaction: (SKPaymentTransaction *)transaction{
        NSLog(@"-----PurchasedTransaction----");
        NSArray *transactions =[[NSArray alloc] initWithObjects:transaction, nil];
        [self paymentQueue:[SKPaymentQueue defaultQueue] updatedTransactions:transactions];
    }
    
    //<SKPaymentTransactionObserver> 千万不要忘记绑定,代码如下:
    //----监听购买结果
    //[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions//交易结果
    {
        NSLog(@"-----paymentQueue--------");
        for (SKPaymentTransaction *transaction in transactions)
        {
            switch (transaction.transactionState)
            {
                case SKPaymentTransactionStatePurchased:{//交易完成
                    [self completeTransaction:transaction];
                    NSLog(@"-----交易完成 --------");
                    
                    UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@""
                                                                        message:@"购买成功"
                                                                       delegate:nil cancelButtonTitle:NSLocalizedString(@"关闭",nil) otherButtonTitles:nil];
                    
                    [alerView show];
                    
                } break;
                case SKPaymentTransactionStateFailed://交易失败
                { [self failedTransaction:transaction];
                    NSLog(@"-----交易失败 --------");
                    UIAlertView *alerView2 =  [[UIAlertView alloc] initWithTitle:@"提示"
                                                                         message:@"购买失败,请重新尝试购买"
                                                                        delegate:nil cancelButtonTitle:NSLocalizedString(@"关闭",nil) otherButtonTitles:nil];
                    
                    [alerView2 show];
                    
                }break;
                case SKPaymentTransactionStateRestored://已经购买过该商品
                    [self restoreTransaction:transaction];
                    NSLog(@"-----已经购买过该商品 --------");
                case SKPaymentTransactionStatePurchasing:      //商品添加进列表
                    NSLog(@"-----商品添加进列表 --------");
                    break;
                default:
                    break;
            }
        }
    }
    - (void) completeTransaction: (SKPaymentTransaction *)transaction
    
    {
        NSLog(@"-----completeTransaction--------");
        // Your application should implement these two methods.
        NSString *product = transaction.payment.productIdentifier;
        if ([product length] > 0) {
            
            NSArray *tt = [product componentsSeparatedByString:@"."];
            NSString *bookid = [tt lastObject];
            if ([bookid length] > 0) {
                [self recordTransaction:bookid];
                [self provideContent:bookid];
            }
        }
        
        // Remove the transaction from the payment queue.
        
        [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        
    }
    
    //记录交易
    -(void)recordTransaction:(NSString *)product{
        NSLog(@"-----记录交易--------");
    }
    
    //处理下载内容
    -(void)provideContent:(NSString *)product{
        NSLog(@"-----下载--------");
    }
    
    - (void) failedTransaction: (SKPaymentTransaction *)transaction{
        NSLog(@"失败");
        if (transaction.error.code != SKErrorPaymentCancelled)
        {
            
        }
        [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        
    }
    -(void) paymentQueueRestoreCompletedTransactionsFinished: (SKPaymentTransaction *)transaction{
        
    }
    
    - (void) restoreTransaction: (SKPaymentTransaction *)transaction
    {
        NSLog(@" 交易恢复处理");
        
    }
    
    -(void) paymentQueue:(SKPaymentQueue *) paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error{
        NSLog(@"-------paymentQueue----");
    }
    
    #pragma mark connection delegate
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        NSLog(@"%@",  [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    }
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection{
        
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
        switch([(NSHTTPURLResponse *)response statusCode]) {
            case 200:
            case 206:
                break;
            case 304:
                break;
            case 400:
                break;
            case 404:
                break;
            case 416:
                break;
            case 403:
                break;
            case 401:
            case 500:
                break;
            default:
                break;
        }
    }
    
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        NSLog(@"test");
    }
    
    -(void)dealloc
    {
        [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];//解除监听
        
    }
    
    @end
    
    我们已经完成了内购的付款操作了,至于如何给到用户商品就在
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
    这个代理方法里面就行操作
    

    你以为这样就完成了么?那你就惨了,APP上线以后你就会发现各种掉单问题,那时你心中肯定有10000只草泥马在奔腾,接下来我们来看看如何避免掉单。

    附:解决掉单篇

    我们先来看看有哪些请况会发生掉单:

    ①. 在ApplePay付款成功后由于网络或各种原因没有返回Transaction(SKPaymentTransaction),从而不能得到凭证去Apple服务器验证订单的正确性。
    ②.苹果服务器成功返回了Transaction,但是在APP在上传凭证给服务器时发生了网络或各种原因,造成了凭证的丢失,产生了掉单(用户付了款却没有得到相应的商品)

    [SKPaymentQueue defaultQueue]这个队列里面存着所有的已支付,未支付的订单,而且需要手动移除,而APP每次启动的时候都会去判断这个队列里面是否为空,如果不为空的话会调用<SKPaymentTransactionObserver>代理的

    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions//交易结果
    
    

    所以我们可以把AppDelegate设置成这个协议的代理并实现这个方法,当然我一般是会写一个遵循<SKPaymentTransactionObserver>的工具类单例,毕竟协议是一对一的,不管是哪里的支付回调,都只走这个类,统一处理。

    上面我们说到每次APP启动时都会判断订单队列是否为空,而且队列需要手动移除,所以我们可以在确保商品已经成功发放到用户手中再做移除操作,这样就完美了。

    移除代码如下:

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    
    

    iOS 7.0后 我们是用[[NSBundle mainBundle] appStoreReceiptURL]来获取凭证。

    注:苹果官方内购验证文档
    https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1

    到这里可能有些童鞋会懵逼了,他会说我付款后怎么和订单关联上啊,完全没有区分的地方啊!
    解决办法如下:当我们创建苹果订单初始化SKPayment时我们应该使用SKMutablePayment,这个类里面有一个参数叫applicationUsername的成员变量,我们可以把后台服务器的订单号写到这里,在付款成功后返回的SKPaymentTransaction里面能拿到这个参数,然后就带着它去请求本地服务器.

    我们把内购搭建好直接进行测试,会提示你购买失败对吧?内购测试我们要到iTunes connection 里去添加沙盒测试员

    1.png
    2.png
    3.png

    然后我们测试的时候换上这个appleId就能进行测试了

    perfect!!!!!!
    哈哈,第一次写技术博客可能写得不好,猿媛们哪里不明白可以在下面提问!!!

    相关文章

      网友评论

      • 8b29bb6873e4:请问一下为什么 每次我一进来 控制器页面就 走 updatedTransactions 这个方法 的交易完成情况啊,然后就弹出输入appleid的alert,我只是再viewdidload里添加了[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        8b29bb6873e4:@羽惊 3q
        羽惊:每个支付完成都得调用[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        如果未调用 这个支付就会一直存在队列中
        你每次进来调用这个[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        就是检测队列中是否有订单未完成
        每次弹出 支付的alert 就是说明你有订单未完成
      • BohrIsLay:订阅型内购,vip按月收费,和按年收费,如果用户已订阅按月收费,删除app后,再次进入,此时还是可以进入vip购买页面,这个时候用户再选择按年收费购买,岂不是又另外收取年费,这个如何控制?
      • Exia_L:请问楼主有没有遇到过正式环境中走- (void)failedTransaction:(SKPaymentTransaction *)transaction 的情况,审核过了一天了,测试环境可以,是不是和苹果服务器有关系啊
      • Alice爱吃鱼:请问下有遇到在请求的时候,一直报无法连接到iTunesstore;或者是请求到了,支付的状态就是fail的问题吗?
      • 择势量投:谢谢分享,很详细,补充下控制器 需要遵循 <SKPaymentTransactionObserver,SKProductsRequestDelegate>
      • 正确的道路上用笨方法:请问 所有内购都抽成?
        圣斗士皮皮:嗯 苹果,谷歌的内测都收的
      • 拉风的胖鱼:请问,你第一个掉单的方法,是在启动APP的时候加上监控就行了么?
      • 剪刀_石头_布:上线审核的时候是给https://sandbox.itunes.apple.com/verifyReceipt这个测试服务器吗?
        圣斗士皮皮:嗯 审核那边是用泥泥沙盒账号测试的
      • d02dde29eab0:想问下“遵循<SKPaymentTransactionObserver>的工具类单例”这个是怎么写的
      • 大斜的张:- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions 这个方法一直返回购买失败的方法 说是无法连接iTunes store
      • Exia_L:请问一下,服务器与苹果服务器验证是怎么弄的,后台不是很清楚怎么弄
      • zeqinjie:请问我app最近内购订单信息获取失败是什么原因,你们有遇到过吗??
      • d96e017e2494:请问 transaction.payment.applicationUsername 为空怎么解决
        887d1fc86fe6:transaction.payment.applicationUsername 这个不能作为信任的值存储。苹果这个字段存在BUG,这个值在某些情况下会为nil, 所以需要换一种存储验证方式,不能依赖这个字段。至今未修复,这个是好久以前就提出的BUG
      • 成功先森:请问,后台订单号如何生成?求个步骤
        圣斗士皮皮:保证唯一即可。时间戳,snowflake,数据库的自增等等
      • 星座是射手座:在ios10.3.2系统上response.products数组返回为0,10.3.1支付就没有问题
      • 海边的樱桃树:[SKPaymentQueue defaultQueue]每次成功后都有finish,但是凭证验证通过返回的订单信息总是包括了所有的历史订单,请问作者遇到过这种情况吗?
      • Aacmr:请问,只要在确保商品已经成功发放到用户手中再做移除操作,[[SKPaymentQueue defaultQueue] finishTransaction: transaction]。你这句话的意思是不是在后台验证成功之后再做移除操作,因为只有后台验证成功了,商品才能发放到客户手中。这样就可以避免漏单了吗?这样就能完美实现了吗? 我看的其他的文章,还要把收据存储到本地,还有失败重发机制,感觉比较麻烦,如果漏单问题,你这个能一句代码解决,真的是再好不过了。
        走在路上的小菜鸟:@Aacmr 可以的,就是你自己管理购买队列,我也是做课程视频啊,你们是做哪块
        Aacmr:@走在路上的小菜鸟 作者呀,我的验证收据是在我们后台服务器验证的。就是不知道漏单丢单移动端要做哪些操作? 就像17楼那个白牛桑说的“要在确保商品发放到用户手上的之后,才执行[[SKPaymentQueue defaultQueue] finishTransaction: transaction]”这句代码,就能解决吗?我做的是购买课程视频。
        走在路上的小菜鸟:这个只是把漏单的问题从移动端转移到服务端,如果是那些单机游戏的话是可以做在移动端,放在服务器端的安全性肯定要比你在本地存储的要好的多,当你付完钱APPLE返回你的支付凭证,你是可以在移动端去像苹果服务器验证这个订单,成功后再去调取服务器的发放商品的接口,但是这样的话安全性得不到保障,很容易被人攻击
      • YYYYYY25:你好,我想问一下,购买成功后,使用base64进行验证,验证成功后,怎么让这个信息失效呢?如果被人抓包,是不是一个购买凭证就无限刷了?
        走在路上的小菜鸟:@YYYYYY25 我也不清楚Apple那边是否可以重复验证,应该是可以的,付完钱要做什么操作是你们决定啊,被人抓包也没有问题,我们这里是这样做的,点击付完款,会获取到苹果返回的一系列信息,然后把这些信息提交给自己的服务器并对应订单,然后由服务器去与苹果服务器进行验证,验证成功则发放商品,并且使那个订单及验证信息失效,这个也是由我们自己控制的,这里也没有任何问题对吧!如果你们自己的接口数据什么的都不进行加密的话被抓包就没法了
      • Aacmr:请问你的掉单处理写哪里了? 标出来行不? 有去服务器验证吗?
      • ab149a495e88:楼主有demo吗,AppDelegate那一块怎么处理的,还是不太清除
      • df0582e56108:①. 在ApplePay付款成功后由于网络或各种原因没有返回Transaction(SKPaymentTransaction),从而不能得到凭证去Apple服务器验证订单的正确性。
        这种情况如何处理
        白牛桑:作者不是说了吗,我们要在确保商品发放到用户手上的之后,才执行[[SKPaymentQueue defaultQueue] finishTransaction: transaction];原理如下:
        [SKPaymentQueue defaultQueue]这个队列里面存着所有的已支付,未支付的订单,而且需要手动移除,而APP每次启动的时候都会去判断这个队列里面是否为空,如果不为空的话会调用<SKPaymentTransactionObserver>代理的- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions//方法

      • 0x00chen:楼主在吗,我用沙盒注册的appleID 支付的时候会出现“验证Apple ID 打开“设置”以继续使用“我的沙盒邮箱”登录”这个弹框,这种情况怎么解决啊,
      • 6a717ad0ba8c:楼主有没有碰到这样的问题, 后台记录了多笔订单,最后和苹果对账的时候,发现少了许多,我怀疑是网上的黑卡造成的。这个对公司的对账来说就多了许多财务问题, 而且也没有证据找到是哪些用户用了黑卡(多半是淘宝代充)。 您有好的解决方法没? :pray:
      • 庄八哥:请问我使用了applicationUsername来记录订单号,但是某些时候,当第二次回调updateTransaction方法时,applicationUsername 是空的,把我之前赋的值丢掉了,所以导致的还是丢单,这种情况怎么处理呢?
        d96e017e2494:@zeqinjie 如何解决?求教
        zeqinjie:@风逝_4940 +1 如何解决好 难道缓存订单号
        风逝_4940:你好,我也遇到这个问题了,请问你是怎么解决的?applicationUsername存储的订单号是多少位的?
      • 56d35fdf1a2a:大神 我是用keychain来存储未完成订单信息的 当点击支付时 先检查keychain中是否有订单 有的话 我调用了一个方法 使[[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 但是为什么没用哇 不会处理[[SKPaymentQueue defaultQueue] addTransactionObserver:self];的方法
      • 超_iOS:applicationUsername这个字段存用户id,和苹果返回的信息一起传给后台是不是就可以了?另外在appdelaget中添加监听(程序刚进来)和(程序挂起时)移除监听是不是也可以解决掉单问题?
        辣椒小鱼:@_超 我也发现 我也有一例 两个订单 票据一样 ,你的解决了吗
        超_iOS:@走在路上的小菜鸟 我现在遇到用户的多次内购,他们的购买凭证都是一样的.这个楼主有什么想法么.想请教
        走在路上的小菜鸟:@李二超 是在APPdelegate里面添加监听 一般username都是存订单号吧,然后付完款就调一下完成订单的接口就可以避免掉单了
      • bb19f132a87c:如果是ios6要怎么关联服务端生成的订单号呢?
      • bb19f132a87c:弱弱问下,apple审核的时候,需要提供一个真实的appid让他测试内购吗?
        走在路上的小菜鸟:@nerver_more 需要啊,提交审核的时候要写上去
        bb19f132a87c:@走在路上的小菜鸟 需要把这个沙盒账号提供给apple测试吗?
        走在路上的小菜鸟:@nerver_more 你可以用开发者账号添加沙盒测试账号的
      • php程序员:像购买视频课程和电子书是不是只能购买一次?还是可以无限制的购买?
        慕诩:层主这个问题解决没?视频课程和电子书这样的属于那种内购类型?价格怎么办
        走在路上的小菜鸟:@php程序员 那要看你的需求是怎么样的
        走在路上的小菜鸟:@php程序员 这要看你们的需求了,需要哪种都能实现
      • imimbluer:如果要在服务器校验交易收据成功返回后再调用finishTransaction的话,请问,服务器返回数据中是不是需要带上订单号。然后遍历transactions中的SKPaymentTransaction,通过订单号找到校验的那个SKPaymentTransaction,在finish掉,是这样吗?
        Coke26:@imimbluer 同问这个方式是否可行,另外怎么给苹果订单附带订单号消息和服务器订单号交验?
      • d079da11e5f9:你在appdelegate 怎么处理的
        走在路上的小菜鸟:@路灯下眺望 这个我倒没有试过
        d079da11e5f9:@路灯下眺望 我想问下你做内购时碰到过如果用户的Apple id 绑定的是信用卡可以购买成功,如果绑定的不是信用卡会产生购买失败!
        走在路上的小菜鸟:@路灯下眺望 appdelegate里面就是实现监听订单回调的方法啊
      • d079da11e5f9:楼主你能把处理掉单的那部分代码贴出来吗?我这个地方遇到了一些问题!谢谢
        d079da11e5f9:@路灯下眺望 我掉单处理是将苹果返回的单号保存在本地,如果后台验证成功就移除,但是不能没有写在appdelegate中
        走在路上的小菜鸟:@路灯下眺望 如果哪里不懂可以问我
        走在路上的小菜鸟:@路灯下眺望 代码已经写在那里啦!你仔细看看吧
      • 294b8c38d51e:楼主写的太好了,最近做内购遇到坑不少还没跳出来,请教一下。我每次购买后,不能及时回调observer的方法,但是下次app启动就出现了上一次购买后写在observer里面的各种打印。还有一个问题,我的产后是消耗型的,为什么当我重复购买,苹果会弹窗说该项目已经购买,已经帮您恢复购买!太无解了,求指导!
        294b8c38d51e:@走在路上的小菜鸟 试了好多次,第一次购买完,updateTansanction那个方法硬是来不了,每次重启app就来了,但是显示上一次的本该打印的结果。还有我把测试账号删了换了一个,然后购买各种提示要我输入密码,默认是之前设置的那个Apple账号,原本以为是延迟,过了一天了居然还是这样,之前的测试号还能购买成功。仔细看了,我就配了一个消耗品,确实是消耗品没错!😔😔😔😔
        走在路上的小菜鸟:@_却真真如果购买完毕回调没有返回,下次启动APP的时候判断购买队列里面有东西的话还会回调的
        走在路上的小菜鸟:@_却真真 如果是消耗类型的,是可以无限制的重复购买,可能是你在ituns里面配错了吧,仔细检查一下
      • LightReason:我不是演双簧,我最近在做内购,遇到了问题会及时骚扰你的 哦
      • 76440382f762:哇,作者写的真不错啊,刚好我也在做这个功能,可以教一下我吗?
        Zrocky:@McMilo 这双簧演的, 忒假了吧

      本文标题:iOS Apple内购及掉单问题

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