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

最近项目由于审核原因需要接入苹果内购,在阅读了大量文章和苹果文档后,比较顺利的完成了开发和提审(目前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 协议,回调中获取可购买的产品信息

获取的信息包括:

  • response.products:可购买的产品列表。
  • response.invalidProductIdentifiers:无效的产品信息。

这个地方有个巨大深坑:如果前面没有签署付费协议、银行账户和报税表没有完成审核、配置产品的信息状态不是“准备提交”状态,只要有一条不满足,就获取不到产品信息。即使以上信息都达到要求,也有可能获取不到产品信息!!!我就遇见了这种情况,最后在阅读了大量文章后,有篇文章建议如果确认后台已经配置好产品信息,可以手动将产品信息添加到购买队列(参考下面第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中的“有效”。
  • App Store Connect 后台配置产品信息是否正确,产品状态是否为“准备提交”。
  • Xcode中是否添加“In-App Purchase”配置项。
  • 当前开发证书的bundle ID和 App Store Connect中配置了产品的 App的bundle ID是否一致。(一定要一致)
  • 是否调试设备中的App 存在缓存,可卸载重装。
  • 确定是真机测试且手机没有越狱。
  • 以上都没问题,还获取不到,可在确保后台配置产品的情况下,先手动将产品添加到购买队列。(参考流程五中第5步)

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

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

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

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

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

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

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


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

Demo参考:IAPManager

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 175,490评论 5 419
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 74,060评论 2 335
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 124,407评论 0 291
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 47,741评论 0 248
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 56,543评论 3 329
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 43,040评论 1 246
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 34,107评论 3 358
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 32,646评论 0 229
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 36,694评论 1 271
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 32,398评论 2 279
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 33,987评论 1 288
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 30,097评论 3 285
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 35,298评论 3 282
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 27,278评论 0 14
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 28,413评论 1 232
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 38,397评论 2 309
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 38,099评论 2 314

推荐阅读更多精彩内容