前言
年初接入第三方支付(微信,支付宝)的时候就在考虑会不会因为虚拟产品(其实就是买会员)被拒,然而侥幸过来第一个版本,之后就悲剧,因为IAP的问题,审核频频悲剧了,后台领导痛下决心(其实无非就是被迫)接内购!
流程
首先: developer.apple.com/account,如果协议(Developer Program License Agreement),需要先同意最新的开发者协议。
其次:https://itunesconnect.apple.com/ 登进去,在职能旁边,有一个: 协议、税务与银行 提交上线审核之前需要填写完整,这个是跟法务和收银相关的,测试阶段可以不填,上线之后必须要填写。
测试账号:苹果提供为了内购提供了一套完整的测试流程给到开发者,在itunesconnect,进入《用户与职能》,点击沙箱技术测试员,创建测试阶段使用的Apple ID(肯定不是用自己的钱来做测试啊)
创建完了测试账号之后,iTunes connect 进入到APP,tabbar切换到功能,有一个APP内购买项目,创建所需要的商品,具体选择的类型得看所对应的场景需要。产品id是唯一的!而且记得区分好中英文大小写类型!!有坑,说不出来诶。
coding相关
Xcode –> Capabilities –> In App Purchase : ON
打开了之后导入静态库StoreKit.framework
导入头文件 #import <StoreKit/StoreKit.h>
遵守的协议<SKPaymentTransactionObserver, SKProductsRequestDelegate>
//设置监听
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
//判断当前设备是否允许使用IAP
if ([SKPaymentQueue canMakePayments]) {
[self requestProductData:ProductID_IAP20];
} else {
DebugLog(@"他喵的不支持内购啊!!!");
}
//请求商品,
- (void)requestProductData:(NSString *)type {
[SVProgressHUD showWithStatus:@"转啊转啊"];
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeBlack];
NSLog(@"-------------请求对应的产品信息----------------");
NSArray *product = [[NSArray alloc] initWithObjects:type,nil];
NSSet *nsset = [NSSet setWithArray:product];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];
}
#pragma mark - SKProductsRequestDelegate
//收到商品回调信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
NSLog(@"--------------收到产品反馈消息---------------------");
NSArray *product = response.products;
if([product count] == 0){
[SVProgressHUD dismiss];
NSLog(@"--------------没有商品------------------");
return;
}
NSLog(@"productID:%@", response.invalidProductIdentifiers);
NSLog(@"产品付费数量:%lu",(unsigned long)[product count]);
SKProduct *p = nil;
for (SKProduct *pro in product) {
NSLog(@"%@", [pro description]);
NSLog(@"%@", [pro localizedTitle]);
NSLog(@"%@", [pro localizedDescription]);
NSLog(@"%@", [pro price]);
NSLog(@"%@", [pro productIdentifier]);
if([pro.productIdentifier isEqualToString:ProductID_IAP0p20]){
p = pro;
}
}
SKPayment *payment = [SKPayment paymentWithProduct:p];
NSLog(@"发送购买请求");
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
//请求失败
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
[SVProgressHUD showErrorWithStatus:@"支付失败"];
NSLog(@"------------------错误-----------------:%@", error);
}
//请求成功并结束
- (void)requestDidFinish:(SKRequest *)request{
[SVProgressHUD dismiss];
NSLog(@"------------反馈信息结束-----------------");
}
//监听购买结果 用户操作付款的回调
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction {
for(SKPaymentTransaction *tran in transaction) {
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased:{
DebugLog(@"交易完成");
[self verifyPurchaseWithPaymentTransaction];
[self completeTransaction:tran];
break;
}
case SKPaymentTransactionStateRestored:{
DebugLog(@"已经购买过商品");
[self restoredTransaction:tran];
break;
}
case SKPaymentTransactionStateFailed:{
DebugLog(@"交易失败");
[self failedTransaction:tran];
[SVProgressHUD showErrorWithStatus:@"购买失败"];
break;
}
case SKPaymentTransactionStatePurchasing:
DebugLog(@"商品正在添加进列表");
break;
default:
break;
}
}
}
/**
* 验证购买,避免越狱软件模拟苹果请求达到非法购买问题
*/
-(void)verifyPurchaseWithPaymentTransaction {
//从沙盒中获取交易凭证并且拼接成请求体数据
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
NSString *receiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串
NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接请求数据
NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
//创建请求到苹果官方进行购买验证
NSURL *url = [NSURL URLWithString:SANDBOX];
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
requestM.HTTPBody = bodyData;
requestM.HTTPMethod = @"POST";
//创建连接并发送同步请求
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:requestM completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
if (error) {
DebugLog(@"发生错了了啊 。。。。。%@",error.localizedDescription);
return;
}
DebugLog(@"%@",dic.allValues);
NSNumber *status = dic[@"status"];
NSString *string = dic[@"environment"];
if (status.integerValue == 0) {
NSLog(@"购买成功!");
NSDictionary *dicReceipt = dic[@"receipt"];
NSDictionary *dicInApp = [dicReceipt[@"in_app"] firstObject];
NSString *productIdentifier= dicInApp[@"product_id"];//读取产品标识
//如果是消耗品则记录购买数量,非消耗品则记录是否购买过
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
if ([productIdentifier isEqualToString:@"123"]) {
NSInteger purchasedCount = [defaults integerForKey:productIdentifier];//已购买数量
[[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier];
}else{
[defaults setBool:YES forKey:productIdentifier];
}
UIAlertController *aaaaa = [UIAlertController alertControllerWithTitle:@"标题" message:@"gewew" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[aaaaa addAction:action];
[self presentViewController:aaaaa animated:YES completion:NULL];
//在此处对购买记录进行存储,可以存储到开发商的服务器端
}
else if (status.integerValue == 21000) {
//App Store无法读取提供的json数据
}
else if (status.integerValue == 21002) {
//收据数据不符合格式
}
else if (status.integerValue == 21003) {
//收据凭证无法被验证
}
else if (status.integerValue == 21004) {
//提供的共享密钥和账户的共享密钥不一致
}
else if (status.integerValue == 21005) {
//收据凭证服务器当前不可用
}
else if (status.integerValue == 21006) {
//凭证是有效的但是订阅的服务已过期,当收到这个信息时,解码后的收据信息也包含在返回内容中
}
else if (status.integerValue == 21007) {
//收据凭证是在sandbox中使用,但却被发送到了生产环境之中使用
DebugLog(@"沙箱环境下却走了正式环境的通道");
}
else if (status.integerValue ==21008) {
//收据凭证是在生产环境中使用,但却被发送到了测试环境之中验证有效性
}
else{
NSLog(@"购买失败,未通过验证!");
}
}] resume];
}
//交易结束
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
DebugLog(@"交易结束");
// NSString * productIdentifier = transaction.payment.productIdentifier;
// NSString * receipt = [transaction.transactionReceipt base64EncodedString];
// if ([productIdentifier length] > 0) {
// // 向自己的服务器验证购买凭证
// }
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
if(transaction.error.code != SKErrorPaymentCancelled) {
DebugLog(@"购买失败");
} else {
DebugLog(@"用户取消交易");
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void)restoredTransaction:(SKPaymentTransaction *)transaction {//重复购买
// 对于已购商品,处理恢复购买的逻辑
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
页面消失时,移除监听
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
以上就是一个完整内购流程的核心代码.但实际还是得跟具体的业务场景相匹配。下面是官方文档上面提供的IAP设计架构流程的图示:
小心,有坑!
虽然说苹果粑粑出产的东西理应很完美,但是国外的支付落后大中华太多了!这里面坑多了去了。
- 首先我们相当于链接国外的服务器,这中间的不确定因素不可谓不小,而且人苹果爸爸直接说了,我们不保障一定没有问题。
- 其次,为了防止越狱,抓包篡改等的问题,购买结束后拿到凭证串之后需要到苹果服务器校验,检验该订单有效之后,才去进行后续的操作,比如,在我们自己的服务器上更新这个订单的有效性。但是!这个过程挂了呢!那就是收了钱不办事,即就是漏单了!所以我们需要将单据信息存储在本地,校验成功了之后,再清除该条数据。自我的理解,这里写成一个单例也不是特别的时候,还有漏多个单的话 网络请求需要对线程做处理,如果是递归调用的话,链式调用的话这个问题还可以不考虑那么多。
末尾
赶鸭子上架,急急忙忙搞出来的IAP功能,不知道还有哪些渗入的坑没踩到,只能后续慢慢更新了。