iOS苹果内购
这边文章对于注册流程和注意事项说的非常的详细了但是里面还是有很多的坑,我自己封了一个类用于针对自己项目的使用地址如下
个人demo里面有个
里面需要注意的几个点:
#pragma mark - SKPaymentTransactionObserver // 监听购买结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
[CXLoadingHud dismissHud];
for (SKPaymentTransaction *tran in transactions) {
//NSLog(@"%ld====",(long)tran.transactionState);
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased: //交易完成
//订阅特殊处理
if(tran.originalTransaction){
//如果是自动续费的订单originalTransaction会有内容
NSLog(@"自动续费的订单,originalTransaction = %@",tran.originalTransaction.transactionIdentifier);
//tran.originalTransaction.transactionIdentifier
//SKPaymentTransaction
[self completeTransaction:tran isAutomatically:YES];
//[self verifyPurchaseWithPaymentTransaction:tran isTestServer:NO];
}else{
//普通购买,以及 第一次购买 自动订阅 第一次自动订阅一定会走这个方法,如果订购成功后一定会走上面的判断
// NSLog(@"%@-------",tran.transactionIdentifier);
[self completeTransaction:tran isAutomatically:NO];
}
break;
case SKPaymentTransactionStatePurchasing://商品添加进列表
#if DEBUG
//NSLog(@"%ld====",tran.error.code);
//NSLog(@"%@====",[[NSString alloc]initWithData:tran.payment.requestData encoding:NSUTF8StringEncoding]);
//[TDGAVirtualCurrency onChargeRequst:@"" iapId:@"" currencyAmount:0 currencyType:@"" virtualCurrencyAmount:0 paymentType:@""];
#endif
break;
case SKPaymentTransactionStateRestored://购买过
#if DEBUG
NSLog(@"已经购买过商品");
#endif
// 消耗型不支持恢复购买
//[[SKPaymentQueue defaultQueue] finishTransaction:tran];
break;
case SKPaymentTransactionStateFailed://交易失败
如果tran.error 打印报错的话,沙盒测试的时候总是报无法连接itunes ,再确保你自己的前期准备工作是ok的话,大概率都是苹果自己的沙盒测试服务器自己gg了,经历过一次,坑爹的玩意,咋都找不出来,最后是苹果自己的问题
NSLog(@"%@====",tran.error);
//SKErrorUnknown
[self failedTransaction:tran];
break;
default:
break;
}
}
}
// 交易结束
- (void)completeTransaction:(SKPaymentTransaction *)transaction isAutomatically:(BOOL)isAutomatically{
// Your application should implement these two methods.
// 票据的校验是保证内购安全完成的非常关键的一步,一般有三种方式:
// 1、服务器验证,获取票据信息后上传至信任的服务器,由服务器完成与App Store的验证(提倡使用此方法,比较安全)我选择的这种,但是会丢单,不过我暂时没有处理,准备用一个plist文件存储下在app再次启动的时候请求下后台,不过这个只是减少了丢单率而且,苹果的支付做得很垃圾
// 2、本地票据校验
// 3、本地App Store请求验证
// NSString * productIdentifier = transaction.payment.productIdentifier;
// NSString * receipt = [transaction.transactionReceipt base64EncodedString];
// if ([productIdentifier length] > 0) {
//
// }
NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];
// 向自己的服务器验证购买凭证
//NSError *error;
//转化为base64字符串
NSString *receiptString=[receipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
//网上转换成base64后后台一直解析不了需要如下处理除去receiptdata中的特殊字符
NSString *receipt1=[receiptString stringByReplacingOccurrencesOfString:@"\n" withString:@""];
NSString *receipt2=[receipt1 stringByReplacingOccurrencesOfString:@"\r" withString:@""];
NSString *receipt3=[receipt2 stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"];
//最终将这个receipt3的发送给服务器去验证就没问题啦!
//自动订阅(自动续费月卡)需要多加一个参数
NSString * product_id = transaction.payment.productIdentifier;
NSString * transaction_id = transaction.transactionIdentifier;
NSMutableDictionary * requestContents = [[NSMutableDictionary alloc]init];
#希望各位处理的时候能将 transaction_id 和 receipt都传给后台因为苹果的receipt解析后里面有很多数据,transaction_id能够帮助后台迅速定位最新的订单,因为测试的时候发现receipt里面有个数组,一般是最后一条数据是最新的,但是坑爹的苹果总是变,有时候是数组的第一条,所以transaction_id至关重要,而且在自动订阅和消耗性两类中,自动订阅的 transaction_id2和transaction_id在第一次请求的时候是一致的,后面的话,要区分只能transaction_id所以这个传给后台至关重要
transaction_id2 = transaction.originalTransaction.transactionIdentifier;
NSString * transaction_id = transaction.transactionIdentifier;
[requestContents addEntriesFromDictionary:@{@"receipt": receipt3,@"password":secretKey,@"product_id":product_id,@"transaction_id":transaction_id,@"originalTransaction":transaction_id2}];
}else{
if (self.parmas.allKeys.count > 0) {
[requestContents addEntriesFromDictionary:@{@"receipt": receipt3,@"uid":self.parmas[@"uid"],@"amount":self.parmas[@"amount"],@"actorid":self.parmas[@"userRoleId"],@"server":self.parmas[@"serverId"],@"order_no":self.parmas[@"cpOrderNo"],@"password":secretKey,@"product_id":product_id,@"transaction_id":transaction_id}];
}
}
NSString * parameters = [self parameters:requestContents];
NSString * url = isAutomatically ? autoURL : consumptionURL;
NSURL *storeURL = [NSURL URLWithString:url];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:[parameters dataUsingEncoding:NSUTF8StringEncoding]];
[storeRequest setTimeoutInterval:30];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask * dataTask = [session dataTaskWithRequest:storeRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//服务器返回的responseObject是gbk编码的字符串,通过gbk编码转码就行了,转码方法如下:
NSString*gbkStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
//转码之后再转utf8解析
NSDictionary * jsonDict = [NSJSONSerialization JSONObjectWithData:[gbkStr dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
if (jsonDict.allKeys.count > 0) {
if ([[jsonDict objectForKey:@"code"]intValue] == 0) {
//[CXLoadingHud showHudWithText:@"购买成功" delay:2];
NSDictionary * dataDict = jsonDict[@"data"];
[[CXInformationCollect collectInfo]fb_mobile_purchase:dataDict[@"amount"] currency:@""];
[[CXInformationCollect collectInfo]af_purchase:@{@"amount":dataDict[@"amount"]}];
}else if ([[jsonDict objectForKey:@"code"]intValue] == 1){
[CXLoadingHud showHudWithText:@"服务器验签失败" delay:2];
}
}
}];
[dataTask resume];
//本地像苹果app store验证,上面是像自己的服务器验证
//[self verifyPurchaseWithPaymentTransaction:transaction isTestServer:NO];
// 验证成功与否都注销交易,否则会出现虚假凭证信息一直验证不通过,每次进程序都得输入苹果账号
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
//[self verifyPurchaseWithPaymentTransaction:transaction isTestServer:NO];
}