美文网首页应用内购iOS
iOS开发 AIP支付总结

iOS开发 AIP支付总结

作者: 方向_4d0d | 来源:发表于2020-04-30 10:40 被阅读0次

    iOS开发 IAP支付总结

    一、IAP介绍

    1.1、简介

    这里先把官方文档给大家

    App 内购买项目配置流程

    内购:只要在iPhone App上购买的不是实物产品(也就是虚拟产品如qq币、鱼翅、电子书......) 都需要走内购流程,苹果这里面抽走30%

    苹果规定,凡是在App内提供的服务需要付费时,必须使用IAP,比如说游戏的金币,道具等;而在App外提供的服务需要付费时,可以使用其他的支付方式,比如支付宝SDK、微信SDK等。说的更通俗一点,如果付费购买的商品是虚拟商品,比如游戏中的道具,并不是现实中存在的,那么必须使用IAP;如果付费购买的商品是真实产品,比如在淘宝中买了件衣服,是实实在在存在的,那么没有必要使用IAP。因此,在使用IAP之前,首先要确认是否一定要使用IAP,如果不使用IAP也可以,那么尽量不要用IAP,因为IAP流程、使用复杂度相比支付宝SDK、微信SDK来说,要复杂很多。

    1.2、内购流程

    1.1.1 填写协议,税务和银行业务

    1、登录https://appstoreconnect.apple.com,选择进入App Store Connect。

    2、进入“协议、税务和银行业务”

    3、内购用的是付费应用程序,先签署《付费应用程序协议》,同意后状态变更为“用户信息待处理”,等待审核。

    4、状态更改完毕后,点击“开始设置税务、银行业务和联系信息”。
    a.添加银行账户,按照要求填写相关内容即可。


    b.选择报税表,并填写。(我是可爱的中国公民,在美国有没有商业活动,所以我填的是否。)

    然后继续填写报税表,按照填写要求填写就行了(要是英文阅读有点困难,那就双击网页,应该会有翻译成中文的功能;没有的话,那就词典。。。你懂得,哈哈哈), 我是个人开发者账户相对公司开发者账户填的会少一点,不过没关系。都是一些基本信息。

    c.填写联系信息,一共5个。高级管理、财务、技术、法务、营销。

    5、上面的税务表填完了之后,点击“我的APP”,进入到项目APP的信息页,点击功能,在弹出的页面点击App内购买项目后面的+。

    创建完成之后 填写内购买项目信息

    信息填写完成了点击右上角的 “存储”,然后点击左边 “App 内购买项目”。出现“元数据丢失”说明里面信息没填写完整,在点进去填写。直到显示“准备提交”。

    6、添加沙箱测试人员

    7、我们需要在工程里配置好证书,测试证书是必须的因为我们内购需要连接到苹果的App Store的,需要正式的测试证书才能测试,同时把下图工程中的这一配置打开

    二、IAP代码部分

    我这里就直接上代码记录了

    2.1、大体代码流程

    typedefenum: NSUInteger {
    
        EPaymentTransactionStateNoPaymentPermission,//没有Payment权限
    
        EPaymentTransactionStateAddPaymentFailed,//addPayment失败
    
        EPaymentTransactionStatePurchasing,//正在购买
    
        EPaymentTransactionStatePurchased,//购买完成(销毁交易)
    
        EPaymentTransactionStateFailed,//购买失败(销毁交易)
    
        EPaymentTransactionStateCancel,//用户取消
    
        EPaymentTransactionStateRestored,//恢复购买(销毁交易)
    
        EPaymentTransactionStateDeferred,//最终状态未确定
    
    } EPaymentTransactionState;
    
    // 这个大家要熟悉哦~
    
    

    步骤一:App Store请求内购项

    注意:此步骤建议在开始创建购买订单前完成,这样可以减少购买时查询订单的时间

    1、判断用户是否具备支付权限

    //是否允许内购
    if ([SKPaymentQueue canMakePayments]) {
        [self getRequestAppleProduct];
    }else{
        [self removeLoadingHandle];
        [self removeIAPObserverHandle];
        dispatch_async(dispatch_get_main_queue(), ^{
            [MBProgressHUDManager showFailedHUD:TZKeyWindow text:@"请打开Apple支付"];
        });
    }
    

    2、创建一个商品查询的请求,productIdentifiers指需要查询的“产品ID”的数组

    - (void)getRequestAppleProduct
    {
        NSLog(@"---------请求对应的产品信息------------");
        [MBProgressHUDManager showHUD:TZKeyWindow text:@"等待响应..."];
        NSArray *product = [[NSArray alloc] initWithObjects:self.productID, nil];
        NSSet *nsset = [NSSet setWithArray:product];
        
        SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
        request.delegate = self;
        
        [request start];
        
    }
    
    查询的结果将通过SKProductsRequestDelegate得到查询的结果
    获取商品的查询结果
    
    #pragma mark - SKProductsRequestDelegate
    //接收到产品的返回信息,然后用返回的商品信息进行发起购买请求
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response NS_AVAILABLE_IOS(3_0){
        
        
        NSArray *product = response.products;
        //没有产品
        if([product count] == 0){
            [self removeLoadingHandle];
            [self removeIAPObserverHandle];
            dispatch_async(dispatch_get_main_queue(), ^{
                [MBProgressHUDManager showFailedHUD:TZKeyWindow text:@"网络开小差了,请稍后重试"];
            });
            return;
        }
        
        
        SKProduct *requestProduct = nil;
        for (SKProduct *pro in product) {
            
            CYLOG(@"描述信息-%@", [pro description]);
            CYLOG(@"产品标题-%@", [pro localizedTitle]);
            CYLOG(@"产品描述信息-%@", [pro localizedDescription]);
            CYLOG(@"价格-%@", [pro price]);
            CYLOG(@"Product id-%@", [pro productIdentifier]);
            CYLOG(@"位置-%@", pro.priceLocale.localeIdentifier);
            
            
            // 确保订单的正确性
            if([pro.productIdentifier isEqualToString:self.productID]){
                requestProduct = pro;
                SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestProduct];
                payment.applicationUsername = self.orderId;
                [[SKPaymentQueue defaultQueue] addPayment:payment];
                break;
            }
        }
    }``
    
    

    步骤二:开始构建购买请求

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

    步骤三:添加支付交易的Observer

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    

    注意在适当的时候移除Observer

    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
    

    可以通过遵循SKPaymentTransactionObserver协议来监听整个交易的过程

    交易状态发生改变时,包括状态的改变,交易的结束

    //监听购买结果
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
        
        NSLog(@"==监听购买结果==");
        
        [self addLoadingHandle];
        [self addIAPObserverHandle];
        
        for(SKPaymentTransaction *tran in transactions){
            switch (tran.transactionState) {
                case SKPaymentTransactionStatePurchased:
                {
                    NSLog(@"交易完成");
                    [self didPurchaseTransaction:tran queue:queue];
                }
                    
                    break;
                case SKPaymentTransactionStatePurchasing:{
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [MBProgressHUDManager showHUD:TZKeyWindow text:@"正在购买..."];
                    });
                }
                    
                    break;
                case SKPaymentTransactionStateRestored:{
                    CYLOG(@"已经购买过商品");
                    //消耗型不用写
    //                                [self removeLoadingHandle];
    //                                [self removeIAPObserverHandle];
    //                                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }
                    break;
                case SKPaymentTransactionStateFailed:{
                    NSLog(@"交易失败");
                    [self removeLoadingHandle];
                    [self removeIAPObserverHandle];
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [MBProgressHUDManager showFailedHUD:TZKeyWindow text:@"交易失败"];
                    });
                }
                    
                    break;
                case SKPaymentTransactionStateDeferred:{
                    NSLog(@"还在队列里");
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [MBProgressHUDManager showHUD:TZKeyWindow text:@"正在购买..."];
                    });
                }
                    
                    break;
                default:
                    break;
            }
        }
    }
    

    步骤四:校验凭证

    我这里直接把我这边相关的思路以及代码提供大家参考了:
    有问题可以随时联系我、我后面会讲一下我所遇到过的坑以及解决方案。

    #pragma mark Transaction State
    
    我这里使用后台校验凭证、更加安全
    - (void)didPurchaseTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue
    {
        CYLOG(@"transaction purchased with product ---%@", transaction.payment.productIdentifier);
        CYLOG(@"transaction ID ---%@", transaction.transactionIdentifier);
        if(transaction.payment.productIdentifier != nil){
            if([self.orderId length] && !self.ischecking){
                //如果这个参数存在,则肯定是通过主动发起购买请求引起的
                //在支付成功后,将parameters中的预订单号存起来,并与苹果的订单号绑定起来,并存储到keychain中
                if([self.orderId length] && transaction.transactionIdentifier){
                    [SAMKeychain setPassword:self.orderId forService:TZServiceKey account:transaction.transactionIdentifier];
                }
            }
        }
        WS(weakSelf);
        // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
        NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
        // 从沙盒中获取到购买凭据
        NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
        
        NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
        NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
        NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
        NSString *applicationUsername = transaction.payment.applicationUsername;
        NSString *productId = transaction.payment.productIdentifier;
        NSString *transactionId = transaction.transactionIdentifier;
        //发送POST请求,对购买凭据进行验证
        //测试验证地址:https://sandbox.itunes.apple.com/verifyReceipt
        //正式验证地址:https://buy.itunes.apple.com/verifyReceipt
        if(applicationUsername.length == 0){
            NSString *savedOrderNumber = [SAMKeychain passwordForService:TZServiceKey account:transactionId];
            if ([savedOrderNumber length]) {
                applicationUsername = savedOrderNumber;//获取到订单号
            }
        }
        if ([applicationUsername length] && [encodeStr length] && [productId length] && [transactionId length]) {
            [[HTTPAPIManager manager] reqeustPayAppleReceiptWithOutTradeNo:applicationUsername receiptData:encodeStr useSandbox:TZPAYSandbox productId:productId transactionId:transactionId success:^(NSURLSessionDataTask * _Nullable task, id  _Nullable responseObject, NSDictionary * _Nullable inforDict) {
                _errTimes = 0;
                weakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:(2.0)
                                                                  target:weakSelf
                                                                selector:@selector(handleTimer:)
                                                                userInfo:@{@"transaction":transaction}
                                                                 repeats:YES];
                [[NSRunLoop mainRunLoop]addTimer:weakSelf.timer forMode:NSDefaultRunLoopMode];
                [weakSelf.timer setFireDate:[NSDate date]];
            } failure:^(NSURLSessionDataTask * _Nullable task, YWHTTPError * _Nullable error, NSDictionary * _Nullable responseDict) {
                _errTimes = 0;
                weakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:(2.0)
                                                                  target:weakSelf
                                                                selector:@selector(handleTimer:)
                                                                userInfo:@{@"transaction":transaction}
                                                                 repeats:YES];
                [[NSRunLoop mainRunLoop]addTimer:weakSelf.timer forMode:NSDefaultRunLoopMode];
                [weakSelf.timer setFireDate:[NSDate date]];
            }];
            if (!_ischecking) {
                [MBProgressHUDManager showHUD:TZKeyWindow text:@"正在确认支付..."];
            }
        } else {
            [[HTTPAPIManager manager] reqeustAppleFailRecordWithOutTradeNo:applicationUsername receiptData:encodeStr productId:productId transactionId:transactionId success:^(NSURLSessionDataTask * _Nullable task, id  _Nullable responseObject, NSDictionary * _Nullable inforDict) {
                _ischecking = NO;
                [MBProgressHUDManager hiddenHUD:TZKeyWindow];
                [weakSelf destroyTimer];
                [weakSelf removeLoadingHandle];
                [weakSelf removeIAPObserverHandle];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                [[NSNotificationCenter defaultCenter] postNotificationName:TZBuyVipSuccessNotification object:nil userInfo:nil];
            } failure:^(NSURLSessionDataTask * _Nullable task, YWHTTPError * _Nullable error, NSDictionary * _Nullable responseDict) {
                [MBProgressHUDManager hiddenHUD:TZKeyWindow];
            }];
        }
    }
    
    

    三、重点总结

    1.获取内购列表(从App内读取或从自己服务器读取)
    
    2.App Store请求可用的内购列表
    
    3.向用户展示内购列表
    
    4.用户选择了内购列表,再发个购买请求,收到购买完成的回调(购买完成
    后会把钱打给申请内购的银行卡内)
    
    5.购买流程结束后, 向服务器发起验证凭证以及支付结果的请求
    
    6.自己的服务器将支付结果信息返回给前端并发放虚拟产品
    
    7.服务端的工作比较简单,分4步:
    
      7.1.接收ios端发过来的购买凭证。
      
      7.2.判断凭证是否已经存在或验证过,然后存储该凭证。
      
      7.3.将该凭证发送到苹果的服务器验证,并将验证结果返回给客户端。
      
       7.4.如果需要,修改用户相应的会员权限。
       
       7.5.考虑到网络异常情况,服务器的验证应该是一个可恢复的队列,如果网络失败了,应该进行重试。
       
    简单来说就是将该购买凭证用Base64编码,然后POST给苹果的验证服务
    器,苹果将验证结果以JSON形式返回。
    

    四、总结坑

    SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestProduct];
    payment.applicationUsername = self.orderId;
    [[SKPaymentQueue defaultQueue] addPayment:payment];
    //我这里刚才只是对订单号的存储、并且把订单号存在了applicationUsername 
    

    上面的处理中、上线后我这里第一个测出了真实支付中的漏单情况、订单号返回的nil,由于对payment.applicationUsername的极度信任、造成自己不得不紧急解决下这个问题发版、不过我事先作的还是有一些功课的、做了埋点、及时的定位到了问题、并且让后台进行了手动补单。相信大家看到这里的时候就不会只是简单的这样做了。

    五、解决方案

    1、针对掉单的问题、网上的资料讨论的太多了我这里简单说下我的方案吧

    我这里进行了对订单号、transactionId、进行对应的钥匙串存储。这样可以解决大部分的异常场景、基本没什么漏单了、并且我对掉单每次启动进行了掉单查询、还有就是再次购买页面也会有相应的提示、让用户自己继续处理、便可以重新提交订单了。方便太多啦

    /// 掉单处理
    - (void)checkIAPHandle{
        _ischecking = YES;
        _isShowErrorM = NO;
        [self addIAPObserverHandle];
    }
    
    
    
    - (void)checkTransactionHandle
    {
        NSArray *transactions = [SKPaymentQueue defaultQueue].transactions;
        if (transactions && [transactions isKindOfClass:[NSArray class]] && [transactions count]) {
            for (SKPaymentTransaction *transaction in transactions) {
                if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
                     NSString *title = @"您有一笔会员订单未完成,请继续处理";
                               NYAlertView *alertView = [[NYAlertView alloc] initWithTitle:@"发现未完成订单"
                                                                                   message:title
                                                                         cancelButtonTitle:nil
                                                                         otherButtonTitles:@"继续处理", nil];
                               [alertView setClickButtonBlock:^(NYAlertView * _Nonnull alert, NSInteger index) {
                                   [[ApplePayManager sharedManager] handleCheckPurchaseTransaction:transaction];
                               }];
                               alertView.titleTextAlignment = NSTextAlignmentLeft;
                               alertView.messageTextAlignment = NSTextAlignmentLeft;
                               [alertView.otherButton setTitleColor:[UIColor wb_colorWithHexString:@"F44A4A"] forState:UIControlStateNormal];
                               [alertView show];
                }
            }
        }
    }
    
    

    六、附言

    大家做内购过程中遇到问题可以随时沟通哈!!! QQ:304517331

    有好的建议也记得及时分享哦😯

    祝大家工作顺利!!!!~~~~

    七、干货

    https://developer.apple.com/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store?language=objc

    59601586421668_.pic.jpg

    相关文章

      网友评论

        本文标题:iOS开发 AIP支付总结

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