内购最新流程:仅限使用swift:https://juejin.cn/post/6974733392260644895
【iOS 内购:自动订阅功能】
1、第一步:调用苹果支付
#pragma mark -- 苹果支付
- (void)ApplePay {
if ([SKPaymentQueue canMakePayments]) {
// 6.请求苹果后台商品
[self getRequestAppleProduct];
}
}
// 请求苹果商品
- (void)getRequestAppleProduct {
// 7. 这里的com.czchat.CZChat01就对应着苹果后台的商品ID,他们是通过这个ID进行联系的。
NSArray *product = [[NSArray alloc] initWithObjects:self.BuyID, nil];
NSSet *nsset = [NSSet setWithArray:product];
// 8.初始化请求
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[SVProgressHUD showWithStatus:@"正在加载"];
// 9.开始请求
[request start];
}
// 10.接收到产品的返回信息,然后用返回的商品信息进行发起购买请求
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
// 如果服务器没有产品
if (products.count == 0) {
NSLog(@"nothing");
return;
}
SKProduct *requestProduct = nil;
for (SKProduct *pro in products) {
NSLog(@"%@", [pro description]);
NSLog(@"%@", [pro localizedTitle]);
NSLog(@"%@", [pro localizedDescription]);
NSLog(@"%@", [pro price]);
NSLog(@"%@", [pro productIdentifier]);
// 11. 如果后台消费条目的ID与我这里需要的请求的一样(用于确保订单的正确性)
if ([pro.productIdentifier isEqualToString:self.BuyID]) {
requestProduct = pro;
}
}
NSLog(@"%@", requestProduct);
//12. 发送购买请求
NSArray *transactions = [SKPaymentQueue defaultQueue].transactions;
if (transactions.count > 0) {
// 检测是否有未完成的交易
SKPaymentTransaction *transaction = [transactions firstObject];
if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
return;
} else {
SKPayment *payment = [SKPayment paymentWithProduct:requestProduct];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
} else {
SKPayment *payment = [SKPayment paymentWithProduct:requestProduct];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
}
// 请求失败
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(@"%@", error);
[SVProgressHUD dismiss];
}
// 反馈请求的产品信息结束后
- (void)requestDidFinish:(SKRequest *)request {
NSLog(@"信息反馈结束");
}
// 13.购买监听结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
NSLog(@"------");
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
NSLog(@"交易完成");
NSLog(@"购买完成,向自己的服务器验证------ %@", transaction.payment.applicationUsername);
NSLog(@"购买完成,向自己的服务器验证------ %@", transaction.payment.hyb_toJsonString);
// 将交易从交易队列中删除
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
if (transaction.originalTransaction) { // 订阅特殊处理
// 如果是自动续费的订单originalTransaction会有内容
} else {
//普通购买,以及 第一次购买 自动订阅
// [self completedTransaction:transaction]; // 本地校验交易凭据
[self getApplePayDataToServerRequsetWith:transaction]; // 后台服务器校验App Store交易凭据
}
break;
case SKPaymentTransactionStatePurchasing:
NSLog(@"商品添加进列表");
break;
case SKPaymentTransactionStateRestored:
NSLog(@"已经购买过商品");
[SVProgressHUD dismiss];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
if (transaction.error.code == SKErrorPaymentCancelled) {
NSLog(@"用户取消了购买请求");
} else if (transaction.error.code == SKErrorCloudServiceNetworkConnectionFailed) {
NSLog(@"设备无法链接到网络");
}
else {
NSLog(@"交易失败!!!");
}
[SVProgressHUD dismiss];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateDeferred:
NSLog(@"已在队列中,等待外部操作");
break;
default:
break;
}
}
}
2、第二步:后台服务器端验证App Store票据(为了安全,需要自己服务器验证App Store票据)
注意:字符串:@"{"receipt-data" : "%@"}",服务器解码时,带上“receipt-data”字段,不然base64解码失败
苹果返回的订单中in_app
是一个数组,要想获取到对应的某一个订单信息,就如下这样:
特别说明:如果你需要当前票据的唯一号,取in_app
中最后一个票据的transaction_id
就行,即transaction.transactionIdentifier
,这个transaction_id
和苹果返回的订单中的transaction_id
比较,获取对应的订单,改变数据库用户的交易记录。
#pragma mark -- -- 后台服务器端验证App Store票据 -- --
/** 注意:这里之后可以不用自己去验证,直接调用自己服务器接口,让后台去APP Store 验证*/
/** 将App Store返回的交易凭据发送到后台服务器,由后台服务器验证,code==200,提示成功 */
// (14.): 交易结束,当交易结束后还要后台服务器端去App Store上验证支付信息是否都正确,只有所有都正确后,我们就可以给用户发放我们的虚拟产品了。
- (void)getApplePayDataToServerRequsetWith:(SKPaymentTransaction *)transaction {
// 唯一transaction_id
NSString *transaction_id = transaction.transactionIdentifier;
NSString *str = [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
NSLog(@"------ 完成交易调用的方法getApplePayDataToServerRequsetWith 1----------");
// 获取设备端app的交易收据数据,使用NSBundle的方法定位app的收据,并用Base64编码。将此 Base64 编码数据发送到您的服务器。
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
if (!receiptData) {
NSLog(@"没有交易收据, NO receipt");
} else {
// 获取编码格式的收据
NSString *encodedReceipt = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
// 注意:字符串:@"{\"receipt-data\" : \"%@\"}",服务器解码时,带上“receipt-data”字段,不然base64解码失败
// NSString *sendString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodedReceipt];
// server解析json字符串和端上一样,不带转义字符“\”根本解析不出来
// 先将凭据转成字典,在将字典转成json字符串(添加了receipt-data)
NSDictionary *sendDic = @{@"receipt-data" : encodedReceipt};
NSData *data = [NSJSONSerialization dataWithJSONObject:sendDic options:NSJSONWritingPrettyPrinted error:nil];
NSString *sendString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[self getApplePayDataToServerRequestString:sendString withTransaction:transaction];
}
}
//(15.):发送到服务器,由服务器判断交易收据(生成预订单,成功后在发送到服务器校验App Store凭据)
- (void)getApplePayDataToServerRequestString:(NSString *)receiptString withTransaction:(SKPaymentTransaction *)transaction {
if(user_uuid == nil)
{
LoginViewCTRL *loginViewCTRL = [[LoginViewCTRL alloc] init];
loginViewCTRL.loginVieCTRLBlock = ^{
};
[self.navigationController pushViewController:loginViewCTRL animated:YES];
return;
}
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString *app_Version = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
NSString *pay_member_id = @"";
if ([self.BuyID isEqualToString:@"1MonthMember"]) {
pay_member_id = @"21";
}else if ([self.BuyID isEqualToString:@"6MontherMember"])
{
pay_member_id = @"23";
}else if ([self.BuyID isEqualToString:@"12MonthMember"])
{
pay_member_id = @"24";
}else if ([self.BuyID isEqualToString:@"buyMember03"])
{
pay_member_id = @"45";
}
NSDictionary *dic = @{
@"user_uuid":wy_user_uuid?:@"",
@"app_version":app_Version,
@"pay_method":@"3",
@"pay_source":@"6",
@"pay_for":self.normalMemberBut.selected?@"1":@"11",
@"client_type":@"2",
@"pay_member_id":[NSString stringWithFormat:@"%@", pay_member_id]
};
NSArray *keyArray = [dic allKeys]; // 将dic中的全部key取出,并放到数组
// 根据ASCII码,将参数key从小到大排序(升序)
NSStringCompareOptions comparisonOptions =NSCaseInsensitiveSearch|NSNumericSearch|NSWidthInsensitiveSearch|NSForcedOrderingSearch;
NSComparator sort = ^(NSString *obj1, NSString *obj2) {
NSRange range = NSMakeRange(0, obj1.length);
return [obj1 compare:obj2 options:comparisonOptions range:range];
};
NSArray *resultArr = [keyArray sortedArrayUsingComparator:sort];
// NSLog(@"字符串数组排序结果%@",resultArr);
NSMutableArray *paramValueArr = [NSMutableArray arrayWithCapacity:resultArr.count];
for (NSString *str in resultArr) {
// 将key对应的value,存到数组,用“7500KM”组成字符串
NSString *tokenStr = [dic objectForKey:[NSString stringWithFormat:@"%@", str]];
if (tokenStr.length > 0) {
[paramValueArr addObject:tokenStr];
}
}
// NSLog(@"字符串数组Value排序结果%@",paramValueArr);
NSString *token = [paramValueArr componentsJoinedByString:@"7500KM"];
token = [JXUtilTool md5HexDigest:token];
NSDictionary *params = @{
@"user_uuid":wy_user_uuid?:@"",
@"app_version":app_Version,
@"pay_method":@"3", // 支付方式 1:阿里 2:微信 3:苹果
@"pay_source":@"6",// 1.二维码支付 2.H5支付 3.支付宝手机网站支付 4.支付宝电脑网站支付 5.微信JSAPI 6.APP
@"pay_for":self.normalMemberBut.selected?@"1":@"11",// 1: 会员 2:金币 3:礼品卡 4:商城 10其它(自定义生成的订单)11阅读会员
@"client_type":@"2", // 1.安卓 2ios 3.pc
@"pay_member_id":[NSString stringWithFormat:@"%@", pay_member_id], // 会员类型:1月、6月、12月、一年
@"token":[NSString stringWithFormat:@"%@", token]
};
__weak typeof(self)weakSelf = self;
[[AFShareManager sharedManager] POST:[AFShareManager LFT_UrlString:@"/pay/member"] parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@", task.currentRequest.URL);
NSInteger reCode = [[responseObject objectForKey:@"reCode"] integerValue];
if (reCode == 200) {
// responseObject = [Tool Handle7500kmresponseObject:responseObject]; // 解码
NSDictionary *dataDic = [NSDictionary dictionary];
dataDic = [responseObject objectForKey:@"data"];
[self getApplePayDataToServerRequestString:receiptString withBookingOrderDictionary:dataDic withTransaction:transaction];
} else {
NSString *message = [responseObject objectForKey:@"reMessage"];
[KAlertViewFactory showToastWithMessage:message];
[SVProgressHUD dismiss];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@", task.currentRequest.URL);
NSLog(@"error ---->>>> %@", error);
[SVProgressHUD dismiss];
}];
}
// (16.)发送到后台服务器,校验App Store凭据
- (void)getApplePayDataToServerRequestString:(NSString *)receiptString withBookingOrderDictionary:(NSDictionary *)bookingOrderDic withTransaction:(SKPaymentTransaction *)transaction {
NSDictionary *params = @{
@"base64Receipt":receiptString,
@"out_trade_no":[NSString stringWithFormat:@"%@", bookingOrderDic[@"out_trade_no"]],
@"price":[NSString stringWithFormat:@"%@", bookingOrderDic[@"price"]],
@"user_uuid":[NSString stringWithFormat:@"%@", bookingOrderDic[@"user_uuid"]]
};
[[AFShareManager sharedManager] POST:[AFShareManager LFT_UrlString:@"/member/verifyReceipt"] parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@", task.currentRequest.URL);
NSInteger reCode = [[responseObject objectForKey:@"reCode"] integerValue];
if (reCode == 200) { // 验证完成,改变展示的信息
[self GainPersonInfo];
[[WL_Tool getInstance] GainUserInfo:self];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"购买成功" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
[alert show];
} else {
NSString *message = [NSString stringWithFormat:@"%@",[responseObject valueForKey:@"reMessage"]];
// [KAlertViewFactory showToastWithMessage:message];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
[alert show];
}
[SVProgressHUD dismiss];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@", task.currentRequest.URL);
NSLog(@"error ---->>>> %@", error);
[SVProgressHUD dismiss];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}];
}
2、第二步:(第二种,不需要服务器验证,自己在客户端验证,不安全,容易被破解,导致赚钱少哦)
#pragma mark -- 方法(completedTransaction:)和方法(SubmitDataTrade_no: memberType: Transaction:)
// 14. 交易结束,当交易结束后还要去App Store上验证支付信息是否都正确,只有所有都正确后,我们就可以给用户发放我们的虚拟产品了。
- (void)completedTransaction:(SKPaymentTransaction *)transaction {
NSString *str = [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
NSString *environment = [self environmentForReceipt:str];
NSLog(@"------ 完成交易调用的方法completedTransaction 1----------%@", environment);
// 验证凭据,获取到苹果返回的交易凭据
// appStoreReceiptURL iOS 7.0 增加的,购买交易完成后,会将凭据存放在该地址
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
// 从沙盒中获取到购买凭据
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
/**
20 BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
21 BASE64 是可以解密的
22 */
NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
NSString *sendString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
NSLog(@"______%@", sendString);
/** 注意:这里之后可以不用自己去验证,直接调用自己服务器接口,让后台去APP Store 验证*/
NSURL *storeUrl = nil;
if ([environment isEqualToString:@"environment=Sandbox"]) {
storeUrl = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
} else {
storeUrl = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
}
// 这个二进制数据由服务器进行验证:zl
NSData *postData = [NSData dataWithBytes:[sendString UTF8String] length:[sendString length]];
NSLog(@"++++++%@", postData);
NSMutableURLRequest *connectionRequest = [NSMutableURLRequest requestWithURL:storeUrl];
[connectionRequest setHTTPMethod:@"POST"];
[connectionRequest setTimeoutInterval:50.0]; // 120.0 ----50.0zl
[connectionRequest setCachePolicy:NSURLRequestUseProtocolCachePolicy];
[connectionRequest setHTTPBody:postData];
// 开始请求
NSError *error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:connectionRequest returningResponse:nil error:&error];
if (error) {
NSLog(@"验证购买过程中发生的错误,错误信息:%@", error.localizedDescription);
return;
}
NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
NSLog(@"请求成功后的数据:%@",dic);
//这里可以等待上面请求的数据完成后并且state = 0 验证凭据成功来判断后进入自己服务器逻辑的判断,也可以直接进行服务器逻辑的判断,验证凭据也就是一个安全的问题。楼主这里没有用state = 0 来判断。
// [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
NSString *product = transaction.payment.productIdentifier;
NSLog(@"transaction.payment.productIdentifier++++%@",product);
if ([product length] > 0)
{
NSArray *tt = [product componentsSeparatedByString:@"."];
NSString *bookid = [tt lastObject];
if([bookid length] > 0)
{
NSLog(@"打印bookid%@",bookid);
//这里可以做操作把用户对应的虚拟物品通过自己服务器进行下发操作,或者在这里通过判断得到用户将会得到多少虚拟物品,在后面([self getApplePayDataToServerRequsetWith:transaction];的地方)上传上面自己的服务器。
NSDictionary *receipt = [dic valueForKey:@"receipt"];
NSArray *in_app = [receipt valueForKey:@"in_app"];
for (int i = 0; i%@",error);
[SVProgressHUD dismiss];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}];
}
}
3、第三步:
// 结束后,一定要销毁
- (void)dealloc {
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
// 在第二步中调用(第二种,不安全的验证票据方法中):
- (NSString *)environmentForReceipt:(NSString *)str {
str = [str stringByReplacingOccurrencesOfString:@"\r\n" withString:@""];
str = [str stringByReplacingOccurrencesOfString:@"\n" withString:@""];
str = [str stringByReplacingOccurrencesOfString:@"\t" withString:@""];
str = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
str = [str stringByReplacingOccurrencesOfString:@"\"" withString:@""];
NSArray *arr = [str componentsSeparatedByString:@";"];
// 存储收据环境的变量
NSString *environment = arr[2];
return environment;
}
4、漏订单处理:
#pragma mark -- Apple内购:漏单情况
- (void)appleRequest {
/** Apple内购:漏单情况 */
//读取用户状态和配置信息到单例中
if ([GWUserInfoContext sharedUserInfoContext].userInfo.token &&[GWUserInfoContext sharedUserInfoContext].userInfo.name.length !=0) {
// GWUserInfoModel *model = [GWUserInfoContext sharedUserInfoContext].userInfo;
NSString *user_id = [NSString stringWithFormat:@"%ld", [GWUserInfoContext sharedUserInfoContext].userInfo.userId];
if (user_id != nil) {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSArray *receiptArray = [userDefaults objectForKey:[NSString stringWithFormat:@"%@Apple_ReceiptData_MemberArray", user_id]];
if (receiptArray != nil) { // 本地有值
if (receiptArray.count > 0) {
NSMutableArray *receipt_MutableArray = [NSMutableArray arrayWithCapacity:0];
receipt_MutableArray = [receiptArray mutableCopy];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 处理耗时操作的代码块...
for (NSDictionary *dic in receipt_MutableArray) {
[self getApplePayWithOrderDictionary:dic]; // 可以多次请求数据库数据
}
//通知主线程刷新
dispatch_async(dispatch_get_main_queue(), ^{
//回调或者说是通知主线程刷新,(这里可以保存、删除一下数据)
});
});
}
}
[userDefaults synchronize];
}
}
}
// (11.)发送到后台服务器,校验App Store凭据
- (void)getApplePayWithOrderDictionary:(NSDictionary *)orderDic {
// [GWUserInfoContext sharedUserInfoContext].userInfo = [GWUtilities GetNSUserDefaults];
NSString *user_id = [NSString stringWithFormat:@"%ld", [GWUserInfoContext sharedUserInfoContext].userInfo.userId];
NSString *payloadStr = [NSString stringWithFormat:@"%@", orderDic[@"receipt-data"]];
NSDictionary *params = @{
@"payload":[NSString stringWithFormat:@"%@", payloadStr],
@"transactionId":[NSString stringWithFormat:@"%@", orderDic[@"transaction_id"]],
@"oid":[NSString stringWithFormat:@"%@", orderDic[@"order_id"]]
};
[BRNetworkHepler postWithUrl:@"/golf/vip/iosPay" params:params headers:nil success:^(GWHttpBaseResponseModel *responseObject, NSString *message) {
NSInteger code = responseObject.code;
if (code == 200) {
NSDictionary *dataDic = responseObject.data;
NSString *order_id = [NSString stringWithFormat:@"%@", dataDic[@"oid"]];
// 票据验证成功:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSArray *receiptArray = [userDefaults objectForKey:[NSString stringWithFormat:@"%@Apple_ReceiptData_MemberArray", user_id]];
if (receiptArray != nil) { // 本地含有储存 票据
NSMutableArray *receipt_MutableArray = [NSMutableArray arrayWithCapacity:0];
receipt_MutableArray = [receiptArray mutableCopy];
for (NSDictionary *dic in receipt_MutableArray) {
if ([[NSString stringWithFormat:@"%@", dic[@"order_id"]] isEqualToString:order_id]) {
[receipt_MutableArray removeObject:dic];
}
}
if (receipt_MutableArray.count > 0) {
NSArray *Apple_ReceiptData_MemberArray = [receipt_MutableArray copy];
[userDefaults setObject:Apple_ReceiptData_MemberArray forKey:[NSString stringWithFormat:@"%@Apple_ReceiptData_MemberArray", user_id]];
} else {
[userDefaults removeObjectForKey:[NSString stringWithFormat:@"%@Apple_ReceiptData_MemberArray", user_id]];
}
} else {
}
[userDefaults synchronize];
} else {
NSLog(@"message ===>>> %@", responseObject.msg);
}
} failure:^(NSError *error, NSString *message) {
NSLog(@"error ====>>>> %@", error);
NSLog(@"message ===>>> %@", message);
}];
}
一些相关网址:
1、https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/persisting_a_purchase?language=objc
2、https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/setting_up_the_transaction_observer_for_the_payment_queue?language=objc
3、iOS 内购(In-App Purchase)总结
https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/validating_receipts_with_the_app_store?language=objc