美文网首页
iOS 内购集成

iOS 内购集成

作者: yahoouchen | 来源:发表于2018-06-09 14:09 被阅读77次

    最近在做内购项目SDK,现将集成过程和集成内购过程中遇到的问题记载下来:

    项目中使用到了中间货币(金币)的形式来进行功能使用,模式是使用RMB换成-金币比如:(1RMB = 10金币),所以会集成第三方的支付平台,使用了微信和支付宝的第三方平台过后,发现审核失败,被苹果拒绝,查了一查原因,才是因为苹果对app内的中间币的购买必须走苹果内购(比如冲点券,比如买钻石....)。所以无奈只有使用苹果内购,由于苹果内购的步骤很多,设置的东西太多,所以将这步骤记录下来。

    首先设置协议

    1.打开itunes Connect,选择协议,税务和银行业务

    协议税务.png

    2.点击Request Contracts(申请合同)下面的,request,点了几个确定和下一步后回到主界面。

    协议税务和银行.png

    Contact info:联系人信息
    Bank info:银行信息
    Tax info:税务信息


    协议税务和银行.png

    3.首先设置联系人信息,点击Contact info下面的 Set up(设置),点击Add New Contract(增加先的联系方式)

    填写完成.png

    4.填写详情
    填写完成后点击save(保存)

    保存信息.png

    5.在下面的所有项目中都选择刚刚填写的信息,选择后点击右下角的done(完成),你可以创建很多联系人,在不同的职务选择不同的联系人。因为我是独立开发,所以我全部填写的我自己。
    Senior Management:高管
    Financial:财务
    Technical:技术支持
    Legal:法务
    Marketing:市场推广

    显示信息.png

    6.设置银行信息,点击Back info下面的Set up,弹出页面

    点击Add Bank Account(添加银行账号)

    添加银行卡.png

    选择china,后点击next。

    添加银行卡选择国家.png

    填写了CNAPS Code后点击Next

    输入银行卡号.png

    会弹出你的银行卡开户地的信息,确认一下点击next

    确认银行卡信息.png

    填写银行卡信息,注意:户主名只能写拼音,比如:李三(Li San)。填完后点击Next

    添加银行卡信息.png

    弹出确定信息页面,在下面打钩后点击Save

    确认保存信息.png

    点击了save后就可以在弹出的页面中选择刚刚填写的卡了。选择后点击Save

    选择填写的卡保存.png

    7.设置税务信息,点击Tax info下面的Set up,此时联系人信息已经变成可以编辑状态,银行信息为浏览状态。

    设置税务信息.png

    弹出的界面中,税务分为三种
    U.S Tax Forms: 美国税务
    Australia Tax Forms:澳大利亚税务
    Canada Tax Forms: 加拿大税务
    这里我选择的美国税务,就是第一个

    选择美国税务.png

    弹出第一个选择,点击submit(提交)后,弹出第二个选择

    提交税务信息.png

    弹出第二个选择,选择后点击submit

    提交税务信息2.png

    弹出第三个页面,填写的资料后点击提交,记得勾选页面上的几个复选框

    提交税务信息3.png

    在提交成功后,状态就变成processing成功

    提交税务信息4.png

    到这里设置的协议就已经设置完了。

    创建项目的内购

    1.进入到项目的APP信息页面,点击功能,在弹出的页面点击App内购买项目后面的➕。

    创建内购项目.png

    2.在弹出的新对话框中选择你需要哪一种服务,由于我的项目需要兑换成消耗的金币,所以我选择第一个。选择后点击创建。

    选择内购项目类型.png

    3.开始填写内购项目信息。填完后点击右上角的存储(所有信息必须填写完整)。

    填写内购项目信息.png

    4.点击存储后,内购列表就会有刚刚创建的内购条目。

    内购条目.png

    你app有几个内购级别就需要依次创建几个条目。

    添加测试账号,用来测试支付功能

    1.点击图中用户和职能

    添加测试账号.png

    2.点击沙盒测试员,然后点击左边的➕按钮。

    添加沙盒测试员.png

    3.设置好信息点击右上角存储就可以,记住里面的邮箱和密码用于支付的时候登陆Apple id


    添加测试员信息.png

    代码集成

    打开自己的项目,创建一个测试类。代码都有注释和步骤,直接上代码。

    注意:

    1.必须用真机测试。
    2.测试的时候必须退出自己的apple ID。弹出页面后登陆沙盒的测试apple id。

    使用的时候首先要导入        #import <StoreKit/StoreKit.h>
    

    先上代码再细分析

    实现观察者监听付钱的代理方法,只要交易发生变化就会走下面的方法

    // 监听交易操作与结果
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
        for(SKPaymentTransaction *tran in transaction){
            switch (tran.transactionState) {
                case SKPaymentTransactionStatePurchased:
                {
                    NSLog(@"交易完成");
                    [self completeTransaction:tran];
                    //// 去验证是否真正的支付成功了
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }break;
                case SKPaymentTransactionStatePurchasing:
                {
                    NSLog(@"商品添加进列表");
                }break;
                case SKPaymentTransactionStateRestored:
                {
                    NSLog(@"已经购买过商品");
                    [self restoreTransaction:tran];
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }break;
                case SKPaymentTransactionStateFailed:
                {
                    NSLog(@"交易失败%@",tran.error);
                    [self failedTransaction:tran];
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }break;
                default:
                {
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }break;
            }
        }
    }
    

    注意:在购买成功后需要释放
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

    请求验证
    获取到票据以后我们通过App Store来验证票据是否真实
    沙盒状态下使用:https://sandbox.itunes.apple.com/verifyReceipt来验证
    生产环境下使用:https://buy.itunes.apple.com/verifyReceipt
    常见的验证状态代码:

    InAppPurchaseValidate.h

    #import <Foundation/Foundation.h>
    
    typedef void (^SuccessBlock)(id response);
    
    typedef void (^FailBlock)(NSError *error);
    
    
    #define KK_RECEIPT_VALIDATAURL @"http://10.0.0.110:8001/api/pay/callback_iap"
    
    @interface InAppPurchaseValidate : NSObject
    
    /**
     获取收据信息
    
     @param successBlock 成功回调
     @param failBlock 失嵊回调
     */
    +(void)loadReceiptWithSuccessBlock:(SuccessBlock)successBlock failBlock:(FailBlock)failBlock;
    
    /**
     验证收据信息
     
     配置KK_RECEIPT_VALIDATAURL 为提交receipt到服务端地址
     @param recepiptString AppStore返回的收据信息
     @param successBlock 成功回调
     @param failBlock 失嵊回调
     */
    +(void)validateWithReceipt:(NSString *)recepiptString successBlock:(SuccessBlock)successBlock failBlock:(FailBlock)failBlock;
    
    
    /**
     合并loadReceiptWithSuccessBlock:与validateWithReceipt:获取recpipt信息并向服务器提交验证
    
     配置KK_RECEIPT_VALIDATAURL 为提交receipt到服务端地址
     @param successBlock 成功回调
     @param failBlock 失嵊回调
     */
    +(void)ValidatReceipteWithSuccessBlock:(SuccessBlock)successBlock failBlock:(FailBlock)failBlock;
    
    
    @end
    

    InAppPurchaseValidate.m 文件

    #import "InAppPurchaseManager.h"
    
    
    static NSMutableArray* productIdentifiers = nil;
    static InAppPurchaseManager* m_pInstance = nil;
    
    @interface InAppPurchaseManager()
    {
        SKProductsRequest *productsRequest;
        SKProduct *startedPaymentProduct;
    }
    @property (nonatomic, copy, readwrite) LoadStoreDidBlock loadStoreDidBlock;
    @property (nonatomic, copy, readwrite) PurchaseStatusBlock purchaseStatusBlock;
    
    @end
    @implementation InAppPurchaseManager
    
    #pragma mark- init
    + (InAppPurchaseManager*) getInstance
    {
        if (m_pInstance == nil){
            m_pInstance = [[InAppPurchaseManager alloc] init];
        }
        return m_pInstance;
    }
    
    + (void) releaseInstance
    {
        if (m_pInstance){
            m_pInstance = nil;
            [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
        }
    }
    
    #pragma mark- ProductId
    - (void)addProductIdentifiers:(NSArray*)identifiers
    {
        if (productIdentifiers == nil)
        {
            productIdentifiers = [[NSMutableArray alloc] init];
        }
        
        [productIdentifiers addObjectsFromArray:identifiers];
        
    }
    
    - (void) clearProductIdentifiers
    {
        if (productIdentifiers)
        {
            [productIdentifiers removeAllObjects];
        }
    }
    
    
    #pragma mark- Public methods
    
    - (void)loadStore:(LoadStoreDidBlock)loadStoreDidBlock
    {
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        
        self.loadStoreDidBlock = loadStoreDidBlock;
    
        [self requestProductData];
        
    }
    
    - (void)requestProductData
    {
        if(productIdentifiers.count==0) {
            NSLog(@"error: no productId");
            return;
        }
        
        NSSet *productIdentifiersSet = [NSSet setWithArray:productIdentifiers];
        productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiersSet];
        productsRequest.delegate = self;
        [productsRequest start];
    }
    
    - (BOOL)canMakePurchases
    {
        return [SKPaymentQueue canMakePayments];
    }
    
    -(void)purchaseWithProductId:(NSString *)identifier purchaseStatusBlock:(PurchaseStatusBlock)purchaseStatusBlock
    {
        self.purchaseStatusBlock = purchaseStatusBlock;
        startedPaymentProduct = nil;
        [self addProductIdentifiers:@[identifier]];
        if(self.productList == nil) {
            __weak typeof(self) weakSelf = self;
            [self loadStore:^{
                if(weakSelf.productList == nil)
                    weakSelf.productList = [[NSArray alloc]init];
                [weakSelf purchaseWithProductId:identifier purchaseStatusBlock:purchaseStatusBlock];
            }];
            return;
        }
        
        for (int i = 0; i < self.productList.count; ++i) {
            SKProduct* p = [self.productList objectAtIndex:i];
            if ([[p productIdentifier] isEqualToString:identifier]) {
                startedPaymentProduct = p;
                break;
            }
        }
        
        if(startedPaymentProduct == nil) {
            NSLog(@"没有找到该商品");
            if(purchaseStatusBlock) purchaseStatusBlock(nil,InAppPurchaseFailure);
            return;
        }
        
        [self paymentWithProduct:startedPaymentProduct];
    }
    -(void)paymentWithProduct:(SKProduct *)product
    {
        if (product == nil) {
            NSLog(@"err: startedPaymentProduct is nil");
            return;
        }
        
        SKPayment *payment = [SKPayment paymentWithProduct:product];
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
    
    
    #pragma mark-  SKProductsRequestDelegate
    /// 接收商品信息
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
    {
        if (self.productList) {
            self.productList = nil;
        }
        self.productList = response.products;
        
        NSMutableArray* productListArray = [[NSMutableArray alloc] init];
        for (int i = 0; i < self.productList.count; ++i) {
            NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
            SKProduct* p = [self.productList objectAtIndex:i];
            [dict setObject:(p.localizedTitle != nil ? p.localizedTitle : @"") forKey:@"localizedTitle"];
            [dict setObject:(p.localizedDescription != nil ? p.localizedDescription : @"") forKey:@"localizedDescription"];
            [dict setObject:p.price forKey:@"price"];
            [dict setObject:p.productIdentifier forKey:@"productIdentifier"];
            [productListArray addObject:dict];
        }
        
        NSMutableArray* invalidProductArray = [[NSMutableArray alloc] init];
        for (NSString *invalidProductId in response.invalidProductIdentifiers)
        {
            [invalidProductArray addObject:invalidProductId];
        }
        
        if(self.loadStoreDidBlock) self.loadStoreDidBlock();
        
    }
    
    #pragma mark - SKPaymentTransactionObserver methods
    
    /// 监听交易操作与结果
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
        for(SKPaymentTransaction *tran in transaction){
            switch (tran.transactionState) {
                case SKPaymentTransactionStatePurchased:
                {
                    NSLog(@"交易完成");
                    [self completeTransaction:tran];
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }break;
                case SKPaymentTransactionStatePurchasing:
                {
                    NSLog(@"商品添加进列表");
                }break;
                case SKPaymentTransactionStateRestored:
                {
                    NSLog(@"已经购买过商品");
                    [self restoreTransaction:tran];
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }break;
                case SKPaymentTransactionStateFailed:
                {
                    NSLog(@"交易失败%@",tran.error);
                    [self failedTransaction:tran];
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }break;
                default:
                {
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }break;
            }
        }
    }
    
    //交易结束
    - (void)completeTransaction:(SKPaymentTransaction *)transaction
    {
        [self recordTransaction:transaction];
        [self provideContent:transaction.payment.productIdentifier];
        [self finishTransaction:transaction status:0];
    }
    
    //交易失败
    - (void)restoreTransaction:(SKPaymentTransaction *)transaction
    {
        [self recordTransaction:transaction.originalTransaction];
        [self provideContent:transaction.originalTransaction.payment.productIdentifier];
        [self finishTransaction:transaction status:1];
    }
    
    - (void)failedTransaction:(SKPaymentTransaction *)transaction
    {
            [self finishTransaction:transaction status:-1];
    }
    
    - (void)finishTransaction:(SKPaymentTransaction *)transaction status:(int)status
    {
        InAppPurchaseStatus inAppPurchasestatus = InAppPurchaseSuccess;
        if(status == 1) inAppPurchasestatus = InAppPurchaseRestore;
        if(status == -1) inAppPurchasestatus = InAppPurchaseFailure;
    
        if(self.purchaseStatusBlock) self.purchaseStatusBlock(transaction, inAppPurchasestatus);
        
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    }
    
    

    在你使用到的 地方直接调用

    // 购买产品
        [[InAppPurchaseManager getInstance] purchaseWithProductId:@"com.test1.020.App009" purchaseStatusBlock:^(SKPaymentTransaction *paymentTransaction, InAppPurchaseStatus status) {
           
            if(status == InAppPurchaseFailure) {
                NSLog(@"未完成支付");
                return;
            }
            
            NSString *productIdentifier = paymentTransaction.payment.productIdentifier;
            
            // 方法一 获取票据并向服务端提交票据信息
            // 需要KK_RECEIPT_VALIDATAURL 配置服务端地址
            {
                [InAppPurchaseValidate ValidatReceipteWithSuccessBlock:^(id responesData) {
                    // 提交成功
                    NSLog(@"服务端已返回验证结果responesData");
                } failBlock:^(NSError *error) {
                    NSLog(@"error:%@",error);
                }];
            }
    

    内购的注意事项

    1.一般发生于首次提交app或添加新商品,当你的app通过审核以后,你发现在生产环境下获取不到商品,这是因为app虽然过审核了,但是内购商品还没有正式添加到苹果的服务器里,耐心等待一段时间就可以啦~

    1. 代码中的_currentProId所填写的是你的购买项目的的ID,这个和第二步创建的内购的productID要一致;本例中是 123。

    2. 在监听购买结果后,一定要调用[[SKPaymentQueue defaultQueue] finishTransaction:tran];来允许你从支付队列中移除交易。

    3. 沙盒环境测试appStore内购流程的时候,请使用没越狱的设备。

    4. 请务必使用真机来测试,一切以真机为准。

    5. 项目Bundle identifier需要与您申请AppID时填写的bundleID一致,不然会无法请求到商品信息。

    6. 真机测试的时候,一定要退出原来的账号,才能用沙盒测试账号

    7. 二次验证,请注意区分宏, 测试用沙盒验证,App Store审核的时候也使用的是沙盒购买,所以验证购买凭证的时候需要判断返回Status
      Code决定是否去沙盒进行二次验证,为了线上用户的使用,验证的顺序肯定是先验证正式环境,此时若返回值为21007,就需要去沙盒二次验证,因为此购买的是在沙盒进行的。

    9.您的应用是否处于等待开发者发布(Pending Developer Release)状态?等待发布状态的IAP是无法测试的。

    10.您的内购项目是否是最近才新建的,或者进行了更改?内购项目需要一段时间才能反应到所有服务器上,这个过程一般是一两小时,也可能再长一些达到若干小时。

    11.您在iTC中Contracts, Tax, and Banking Information项目中是否有还没有设置或者过期了的项目?不完整的财务信息无法进行内购测试。

    12.您是在越狱设备上进行内购测试么?越狱设备不能用于正常内购,您需要重装或者寻找一台没有越狱的设备。

    13.您的应用是否是被拒状态(Rejected)或自己拒绝(Developer Rejected)了?被拒绝状态的应用的话对应还未通过的内购项目也会一起被拒,因此您需要重新将IAP项目设为Cleared for Sale。

    14.您使用的测试账号是否是美国区账号?虽然不是一定需要,但是鉴于其他地区的测试账号经常抽风,加上美国区账号一直很稳定,因此强烈建议使用美国区账号。正常情况下IAP不需要进行信用卡绑定和其他信息填写,如果你遇到了这种情况,可以试试删除这个测试账号再新建一个其他地区的。

    15.您是否将设备上原来的app删除了,并重新进行了安装?记得在安装前做一下Clean和Clean Build Folder。

    16.您的plist中的Bundle identifier的内容是否和您的AppID一致?

    文章有点长~~~
    最后附上小demo:
    内购集成Demo

    相关文章

      网友评论

          本文标题:iOS 内购集成

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