Flutter全栈探索面试宝点

iOS苹果内购代码(IAP)和问题记录(2023)

2023-04-04  本文已影响0人  wuyukobe

最近项目由于审核原因需要接入苹果内购,在阅读了大量文章和苹果文档后,比较顺利的完成了开发和提审(目前App Store已审核通过)。这里记录下整个开发流程中相关的代码开发和一些踩过的坑。

一、代码开发

1、引入苹果框架 StoreKit

#import <StoreKit/StoreKit.h>

2、校验设备是否支持内购支付

[SKPaymentQueue canMakePayments];

3、从苹果后台获取配置的产品是否存在

// productsId 为 App Store Connect 后台配置的产品ID
NSSet *identifiers = [NSSet setWithObjects:productsId, nil];
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers];
productsRequest.delegate = self;
[productsRequest start];

4、遵守 SKProductsRequestDelegate 协议,回调中获取可购买的产品信息

获取的信息包括:

这个地方有个巨大深坑:如果前面没有签署付费协议、银行账户和报税表没有完成审核、配置产品的信息状态不是“准备提交”状态,只要有一条不满足,就获取不到产品信息。即使以上信息都达到要求,也有可能获取不到产品信息!!!我就遇见了这种情况,最后在阅读了大量文章后,有篇文章建议如果确认后台已经配置好产品信息,可以手动将产品信息添加到购买队列(参考下面第5步中添加产品到购买队列),执行一次购买之后,当前回调方法就可以正常获取产品信息了。

#pragma mark - SKProductsRequestDelegate
/// 获取商品信息代理回调
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSArray *products = [NSArray arrayWithArray:response.products];
    NSArray *invalidProductIdentifiers = [NSArray arrayWithArray:response.invalidProductIdentifiers];
    
    /// 获取可购买产品id
    if (products.count > 0) {
        for (SKProduct *product in products) {
            NSLog(@"可购买产品id: %@", product.productIdentifier);
            [self addPayment:product];
        }
    } else {
        /// 获取不到产品则手动添加产品到购买队列(确保苹果后台已配置相应产品id)
        SKMutablePayment *payment = [[SKMutablePayment alloc] init];
        // payment.applicationUsername = @"_orderId12"; //透传参数。可以传你自己的订单号,后续可能用得到
        payment.productIdentifier = self.productsId;
        payment.quantity = 1;
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
    /// 无效的产品id
    [invalidProductIdentifiers enumerateObjectsUsingBlock:^(NSString *invalid, NSUInteger idx, BOOL *stop) {
        NSLog(@"无效的产品id: %@", invalid);
    }];
}

遵守SKRequestDelegate 协议,可以获取请求完成和失败的方法:

#pragma mark - SKRequestDelegate
- (void)requestDidFinish:(SKRequest *)request {
    NSLog(@"%s",__func__);
}

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
    NSLog(@"%s",__func__);
    NSLog(@"error userInfos: ",error.userInfo);
    NSLog(@"error _domain: ",error.domain);
}

5、获取到信息之后,添加产品到购买队列

/// 添加到购买队列-调起支付
- (void)addPayment:(SKProduct *)product {
    SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

如果未获取产品信息,则手动创建产品并添加到购买队列

/// 获取不到产品则手动添加产品到购买队列(确保苹果后台已配置相应产品id)
SKMutablePayment *payment = [[SKMutablePayment alloc] init];
/// 透传参数。可以传你自己的订单号,后续可能用得到
// payment.applicationUsername = @"_orderId12";
/// productsId 为 App Store Connect 后台配置的产品ID
payment.productIdentifier = productsId;
payment.quantity = 1;
[[SKPaymentQueue defaultQueue] addPayment:payment];

将产品添加到购买队列后,则会提示在弹窗中输入沙盒账号,输入沙盒账号之后,会将账号保存到设置- App Store -沙盒账户。此后会调起苹果支付的弹窗,点击购买或者取消弹窗,即可在流程6回调方法中获取到相应的支付结果。

6、添加观察者 SKPaymentTransactionObserver,监听后续的支付结果

/// 添加观察者
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
/// 移除观察者
- (void)dealloc {
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
    NSLog(@"%s",__func__);
}

代理方法监听后续的支付结果:

#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased: { /// 交易成功
                NSLog(@"【支付结果】======= 交易成功");
                [self transactionSucceed:transaction];
            }
                break;
            case SKPaymentTransactionStateFailed: { /// 交易失败
                NSLog(@"【支付结果】======= 交易失败");
                [self transactionFailed:transaction];
            }
                break;
            case SKPaymentTransactionStatePurchasing: { /// 商品添加进列表
                NSLog(@"【支付结果】======= 商品添加进列表");
            }
                break;
            case SKPaymentTransactionStateRestored: { /// 已购买过该商品
                NSLog(@"【支付结果】======= 已购买过该商品");
                [self transactionFailed:transaction];
            }
                break;
            case SKPaymentTransactionStateDeferred: { /// 交易延迟
                NSLog(@"【支付结果】======= 交易延迟");
                [self transactionFailed:transaction];
            }
                break;
            default:
            {
                [self transactionFailed:transaction];
            }
                break;
        }
    }
}

7、交易成功之后,将获取的交易凭证发送给后端,让后端去苹果服务器进行校验

/// 交易结束-成功
- (void)transactionSucceed:(SKPaymentTransaction *)transaction {
    /// 这里的URL测试环境下为沙盒url,上线版本中应为苹果后台的URL
    NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
    /// 转化为base64字符串
    NSString *receiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    NSLog(@"交易成功 receiptUrl = %@",receiptUrl);
    NSLog(@"交易成功 receiptData = %@",receiptData);
    NSLog(@"交易成功 receiptString = %@",receiptString);
    
    /// 客户端请求苹果接口进行票据校验(该操作由服务器来验证)
//    NSString *SandboxUrl = @"https://sandbox.itunes.apple.com/verifyReceipt";
//    NSString *url = @"https://buy.itunes.apple.com/verifyReceipt";
//    [self localReceiptVerifyingWithUrl:SandboxUrl AndReceipt:receiptString AndTransaction:transaction];
    // 向自己的服务器验证购买凭证(此处应该考虑将凭证本地保存,对服务器有失败重发机制)
    /**
     服务器要做的事情:
     接收ios端发过来的购买凭证。
     判断凭证是否已经存在或验证过,然后存储该凭证。
     将该凭证发送到苹果的服务器验证,并将验证结果返回给客户端。
     如果需要,修改用户相应的会员权限
     */
    // 监听购买结果,当失败和成功时代码中要调用如下代码:
    // 该方法通知苹果支付队列该交易已完成,不然就会已发起相同 ID 的商品购买就会有此项目将免费恢复的提示。
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];    
}

同时处理交易失败:

/// 交易失败
- (void)transactionFailed:(SKPaymentTransaction *)transaction {
    if (transaction.error.code == SKErrorPaymentCancelled){
        NSLog(@"您取消了内购操作.");
    } else {
        NSLog(@"内购失败,原因:%@",[transaction.error localizedDescription]);
    }
    NSLog(@"transactionFailed with tid: %@ and code:%li and msg:%@", transaction.transactionIdentifier,(long)transaction.error.code,transaction.error.localizedDescription);
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
备注:

1、不管交易成功还是失败,处理完逻辑一定要调用 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; 告诉苹果已完成当前操作,不然之后再购买就会失败!
2、共享密钥 password 获取路径:App Store Connect 后台-用户和访问-共享密钥。获取共享密钥
3、这里获取到的交易凭证比较长,如果要是发给后端先调试逻辑的话一定要复制完整。大约有5000个字节(搞不懂为啥要这么长)。参考如下:

{"receipt-data":"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",
"password":"",
"exclude-old-transactions":true 
}

8、这里提供一个方法,客户端可以手动去苹果服务器进行票据校验(只是测试而已,建议交给后端去做校验)

/// 请求苹果接口进行票据校验
- (void)localReceiptVerifyingWithUrl:(NSString *)requestUrl AndReceipt:(NSString *)receiptStr AndTransaction:(SKPaymentTransaction *)transaction
{
    NSDictionary *requestContents = @{@"receipt-data": receiptStr,};
    NSError *error;
    // 转换为 JSON 格式
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                          options:0
                                                            error:&error];
    NSString *verifyUrlString = requestUrl;
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:[[NSURL alloc] initWithString:verifyUrlString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:requestData];
    
    // 在后台对列中提交验证请求,并获得官方的验证JSON结果
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:storeRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"链接失败");
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        } else {
            NSError *error;
            NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
            if (!jsonResponse) {
                NSLog(@"验证失败");
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            }
            NSLog(@"验证成功");
            //TODO:取这个json的数据去判断,道具是否下发
        }
    }];
    [task resume];
}

以上就是相关代码。

二、问题记录

1、获取不到产品信息

2、代理方法中获取不到支付结果

问题:能够获取产品信息,并将产品添加购买队列中,但是在代理方法 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions 中监听不到支付结果?

解决:检查当前类在调用完成后是否直接被dealloc了,要确保将产品添加到购买队列后当前类仍然存活,不会提前被销毁。不然就接收不到代理回调,也就监听不到支付结果。可以使用单例来保证类的存活。

3、苹果应用内支付,您已购买此App内购买项目。此项目将免费恢复

问题:使用沙箱账号(TestFlight邀请的账号没问题)重复充值同一个消耗型项目时,普通包没问题,但TestFlight包却提示"苹果应用内支付,您已购买此App内购买项目。此项目将免费恢复",表示不能重复充值?

原因:苹果的服务器对大陆访问很慢(特别在沙盒测试环境更加不好),导致用户在购买成功后,收不到苹果的IAP支付成功通知,交易就会一直卡在那里,没有关闭掉,后面无论怎样支付都是提示 “您已购买此App内购买项目。此项目将免费恢复”。

结果:不用解决,直接审核也能通过。目前猜测:1、审核人员用的是他们自己的沙箱账号;2、服务器在美国,审核人员也在美国。可能访问比较快。:)


如果开发过程中遇见了其他无法解决的问题,建议可以大量阅读网上有关内购的文章,以及苹果官方的内购文档。有可能在读到某句话时,你的问题就瞬间迎刃而解了。

Demo参考:IAPManager

上一篇下一篇

猜你喜欢

热点阅读