整个过程需要客户端,服务器,apstore参与
流程:
1.客户端向服务器发送请求订单号,这时服务器产生一个唯一的订单号返回给客户端,并且将订单号存储到数据库(这一步为了防止漏单及刷单的情况)
2.客户端请求到订单号后 开始向苹果支付(先想appstore请求商品信息,然后付款)
3.付款成功后appstore会给客户端返回 一个验证信息(用来验证是否合法)
4.客户端将订单号和验证信息发给客户端
5.服务器向appstore验证是支付是否合法(来判断是否给用户加商品)
1.客户端向服务器请求订单号
用afn或者其他任何方法(随便啦,只要能请求到就ok 这里用afn做测试)
NSString *userid = @"100002"; //标记用户NSString *price = @"3"; //标识商品NSDictionary *parametersDic = [[NSDictionary alloc]initWithObjectsAndKeys:userid,@"userid",price,@"price", nil];AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];[manager GET:@"http://192.168.2.104:3000/apay" parameters:parametersDic progress:^(NSProgress * _Nonnull downloadProgress) {} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {//接收服务器返回的订单号//在这里调起苹果支付(即下面的getProduct方法)NSLog(@"send verify message succeed:%@",responseObject);} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {NSLog(@"send verify message filure:%@",error);}];
2.开始苹果支付阶段
//2.1先向appstore请求商品信息
//查询是否可以内购并且获取产品信息(实际应从服务器中获取)
- (void)getProduct{if ([SKPaymentQueue canMakePayments]) {// 监听购买结果[[SKPaymentQueue defaultQueue] addTransactionObserver:self];NSSet *set = [NSSet setWithArray:@[@"com.socol.12",@"comsocol.68",@"com.socol.168"]];//这里在ituns里面配置SKProductsRequest * request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];request.delegate = self;[request start]; //发送请求 ,返回的结果会走SKProductsRequestDelegate}else{NSLog(@"失败,用户禁止应用内付购买");}
}
//2.2选择用户需要的商品开始购买
#pragma mark - SKProductsRequestDelegate 协议方法
//当开始购买后将回调该方法返回查询的结果
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {NSArray *myProduct = response.products;if (myProduct.count == 0) {NSLog(@"无法获取产品信息,购买失败。");return;}NSLog(@"所有产品信息如下");for (SKProduct *pro in myProduct) {NSLog(@"localizedDescription - %@",pro.localizedDescription);//产品描述NSLog(@"price - %@",pro.price);//价格NSLog(@"localizedTitle - %@",pro.localizedTitle);//产品标题NSLog(@"priceLocale - %@",pro.priceLocale);//产品地域标识NSLog(@"productIdentifier - %@",pro.productIdentifier); //产品idNSLog(@"downloadable - %d",pro.downloadable);//产品描述NSLog(@"downloadContentLengths - %@",pro.downloadContentLengths);//产品描述NSLog(@"downloadContentVersion - %@",pro.downloadContentVersion);//产品描述NSLog(@"----------------------------------------------------------------");}//这里购买某一件商品(符合用户需求的)for (SKProduct *pro in myProduct) {if (@“3" isEqualToString:[pro.price stringValue]]) {product = pro;SKPayment * payment = [SKPayment paymentWithProduct:product];[[SKPaymentQueue defaultQueue] addPayment:payment];//开始购买 购买结果也会走SKPaymentTransactionObserverbreak;}}
}
//2.3购买结果
#pragma mark -SKPaymentTransactionObserver方法
//当注册监听购买结果后将调用该方法
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {for (SKPaymentTransaction *transaction in transactions){switch (transaction.transactionState){case SKPaymentTransactionStatePurchased://交易完成[self completeTransaction:transaction];//调用交易成功的方法break;case SKPaymentTransactionStateFailed://交易失败NSLog(@"发生错误:%@",transaction.error);[self failedTransaction:transaction];break;case SKPaymentTransactionStateRestored://已经购买过该商品[self restoreTransaction:transaction];break;case SKPaymentTransactionStatePurchasing: //商品添加进列表NSLog(@"商品添加进列表");case SKPaymentTransactionStateDeferred: //商品添加进列表NSLog(@"等待外部操作");break;default:break;}}
}
3.购买成功后appstore返回给客户端 验证信息
//购买成功
- (void)completeTransaction:(SKPaymentTransaction *)transaction {// Your application should implement these two methods.NSLog(@"购买成功-----单号:%@",transaction.transactionIdentifier);NSData *datas = [transaction.transactionReceipt base64EncodedDataWithOptions:0];NSString *datasStr = [[NSString alloc]initWithData:datas encoding:NSUTF8StringEncoding];NSLog(@"验证信息%@",datasStr);//本地存储购买凭证(防止验证信息发送不到服务器的情况,每当重启应用都应该检测userdefault里面是否有pripayid值,如果有重新向服务器发送验证信息 防止漏单 当服务器接收到验证信息后清空userdefault)[[NSUserDefaults standardUserDefaults] setObject:self.prepayid forKey:@"prepayid"];[[NSUserDefaults standardUserDefaults] setObject:datasStr forKey:@"verifyString"];//购买成功后通知服务器[self verifyTransaction:datasStr withOrderString:@“用户的userid"];// Remove the transaction from the payment queue.[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}//购买失败
- (void)failedTransaction:(SKPaymentTransaction *)transaction {if(transaction.error.code != SKErrorPaymentCancelled) {NSLog(@"购买失败");} else {NSLog(@"用户取消交易");}//购买失败通知服务器[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
//重复购买
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {// 对于已购商品,处理恢复购买的逻辑NSLog(@"已购买过该商品");//重复购买通知服务器[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
4.将验证信息发送给服务器
//通知服务器
- (void)verifyTransaction:(NSString *)transactionString withOrderString:(NSString *)orderString{NSDictionary *parametersDic = [[NSDictionary alloc]initWithObjectsAndKeys:orderString,@"orderString",transactionString,@"verifyString", nil];AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];[manager POST:@"http://192.168.2.111:3000/notify" parameters:parametersDic progress:^(NSProgress * _Nonnull uploadProgress) {} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {NSLog(@"send verify message succeed:%@",responseObject); //如果验证信息成功发送给服务器后就清空userdefault 否则每次重启应用都会向服务器发送验证信息 防止漏单[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"orderString"];[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"verifyString"];} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {NSLog(@"send verify message filure:%@",error);[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"orderString"];[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"verifyString"];}];
}
5.服务器将验证信息发送到appstore进行验证
这里有两个验证地址
沙盒验证地址: https://sandbox.itunes.apple.com/verifyReceipt
正式验证地址: https://buy.itunes.apple.com/verifyReceipt
这里有一个扯淡的地方:苹果审核时是在沙盒环境下测试的,所以我们上线的代码必须要向沙盒服务器验证,但是通过审核通过后必须向正式服务器验证才会成功 ,有两种解决办法,
1:在苹果审核之前服务器的验证地址为沙盒地址,当苹果审核通过后再把服务器的验证地址改为正式验证地址
2:先向沙盒验证地址发送请求 当返回210007时再向正式地址验证(如果正式购买的验证信息发送到沙盒验证地址验证时将会返回210007
格式:
{ 'receipt-data': ‘验证信息’}
如果返回数据 status:0 则表示验证成功 否则失败 (参考: http://www.cnblogs.com/zhaoqingqing/p/4597794.html)
注意:
1.防止漏单: 当验证服务器没有接收到验证信息的时候就会漏单 (因为服务器是根据验证信息来判断用户是否购买成功的)
解决办法:当客户端购买成功appstore给客户端返回验证信息时将orderid 和验证信息一起存到本地(userdefault或者其他), 然后向服务器发送orderid 和验证信息。如果服务器正确接收(客户端接收到服务器的返回信息),就将本地的order和验证信息删除。 每当应用重启就检测本地是否存有orderid和验证信息,如果有就重发验证信息给服务器
2.防止刷单:如果服务器只单纯的检测验证信息是否合法就判断购买是否成功会有问题,比如下面情况:客户端支付成功以后,不断的向服务器发送同一个验证信息,服务器每次接收到的到验证信息都是合法的,但是实际上用户只支付了一次
解决办法:服务器在客户端第一次请求orderid时就将orderid 存到数据库,并标记一个字段为0表示未验证 ,当接收到客户端的验证信息后,向appstore先进行验证,并且将这个标记更新为1(已验证),下次客户端发来验证信息时如果查询数据库中该订单已验证过了,就不再向appstore进行验证,这就避免了重复验证刷单的情况了。
实际上还有其他的情况:
1:客户端先支付成功一次,这时拿到验证信息 向服务器发送验证信息,这次肯定验证成功 ,以后不断的请求orderid 并且拿新生产的orderid 和第一次支付成功的验证信息 向服务器验证这时也会成功 实际上用户只支付了一次
2: 客户端第一次请求一个1元钱的商品orderid并且支付 这时拿到验证信息不向服务器验证,第二次再请求一个100元商品的orderid 但是不支付,最后拿第一次的验证信息和第二次的orderid 向服务器发送请求验证,这时也会成功,这样就可以实现1元购买100元商品的目的。
所以只是拿appstore返回的status来判断还是不够的,这时解决办法可以从appstore验证的返回信息中做判断。 当然下面的两种情况基本上不会出现,只是也是漏洞,一般上面5步就足够应对苹果支付了。