衣带渐宽终不悔,为伊消得人憔悴,经过近几天的刻苦研读和高效整理,终有成果!
目录:
一.应用内支付Code展示学习
二.支付深度理解层次
三.支付遇到的一些坑
四.手把手集成我的Demo
一.应用内支付Code展示学习
接着上篇文章最后对应用内支付流程梳理步骤进行深化;本篇同样分为支付前,支付中,支付后流程。
1.支付前Code展示
1.1注册通知以下3个通知:
-(void)addAllNoti {
// 获取商品信息通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseToOrderDatas:)
name:IAPZWGoodsRequestNotification
object:[IAPZWGoodsManager sharedInstance]];
// 是否下单成功的通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseToAllGoodsIsSuccess:)
name:IAPZWBuyResultNotification
object:[IAPZWGoodsNoti sharedInstance]];
// 取消支付的通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseToAllGoodsCancle:)
name:IAPZWBuyResultNotificationCancle
object:[IAPZWGoodsNoti sharedInstance]];
}
当然也要在dealloc方法中注销通知
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:IAPZWGoodsRequestNotification
object:[IAPZWGoodsNoti sharedInstance]];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:IAPZWBuyResultNotification
object:[IAPZWGoodsNoti sharedInstance]];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:IAPZWBuyResultNotificationCancle
object:[IAPZWGoodsNoti sharedInstance]];
}
1.2获取商品信息的方法:
// 获取所有的商品信息
-(void)getAllAppleProductInfoDatas
{
// Query the App Store for product information if the user is is allowed to make purchases.
// Display an alert, otherwise.
if([SKPaymentQueue canMakePayments])
{
[[DisplayHelper shareDisplayHelper]showLoading:self.view noteText:@"加载商品中..."];
// 正确方式为从后台获取,这里本地写死商品id
NSArray *productIds = @[@"1008612345",@"1008611111"];
[[IAPZWGoodsManager sharedInstance] requestGetAllGoodsProductIds:productIds];
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// [[DisplayHelper shareDisplayHelper]hideLoading:self.view];
//
// });
}
else
{
}
}
1.3在刚才的通知中回调对应的数据如下方法:
-(void)responseToOrderDatas:(NSNotification *)notification
{
IAPZWGoodsManager *goodsManager = (IAPZWGoodsManager*)notification.object;
IAPGoodsRequestStatus status = goodsManager.status;
[[DisplayHelper shareDisplayHelper]hideLoading:self.view];
if (status == IAPGoodsRequestResponse)
{
self.dataArr = goodsManager.availableProducts;
[self.tableView reloadData];
}
}
2.支付中Code展示(类似微信支付和支付宝支付的下单流程)
2.1用户点击Cell发起下单请求:
// 下单商品
-(void)buyAllGood:(SKProduct *)product
{
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
注意如果发现上次还有未验证的订单可通过下面方法重新验证:
// 如果存在的话即为上次未来的及验证的订单重新验证(这里没有做相关逻辑)
-(void)restoreAllGoods
{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
2.2在调起苹果支付页面后回调到以下这个方法
// 支付过程中的回调
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for(SKPaymentTransaction * transaction in transactions)
{
switch (transaction.transactionState )
{
// 1.第一次调起苹果支付框时走这里
case SKPaymentTransactionStatePurchasing:
break;
case SKPaymentTransactionStateDeferred:
break;
// 2.这里有时会走2次,其中比较关心的一次是 IAPZWBuyGoodsSucceeded 这一次
case SKPaymentTransactionStatePurchased:
{
self.purchasedID = transaction.payment.productIdentifier;
[self.goodProductIds addObject:transaction];
if(transaction.downloads && transaction.downloads.count > 0)
{
[self completeTransaction:transaction forStatus:IAPZWDownOrderStarted];
}
else
{
[self completeTransaction:transaction forStatus:IAPZWBuyGoodsSucceeded];
}
}
break;
case SKPaymentTransactionStateRestored:
{
self.purchasedID = transaction.payment.productIdentifier;
[self.goodRestoredIds addObject:transaction];
NSLog(@"SKPaymentTransactionStateRestored %@",transaction.payment.productIdentifier);
if(transaction.downloads && transaction.downloads.count > 0)
{
[self completeTransaction:transaction forStatus:IAPZWDownOrderStarted];
}
else
{
[self completeTransaction:transaction forStatus:IAPZWRestoredSucceeded];
}
}
break;
// 3.测试中会发现有时走这个方法
case SKPaymentTransactionStateFailed:
{
self.errorMsg = [NSString stringWithFormat:@"SKPaymentTransactionStateFailed %@ ",transaction.payment.productIdentifier];
[self completeTransaction:transaction forStatus:IAPZWBuyGoodsFailed];
}
break;
default:
break;
}
}
}
注意:在调用第一次弹出支付框,如下所示
输入账号和密码后,且密码正确的情况下,点击好后会调用第二次该方法,之后可能调用第三次该方法
2.3在成功调用上面方法之后,会通过以下方式发通知
// 完成支付
-(void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(NSInteger)status
{
self.status = status;
if (transaction.error.code != SKErrorPaymentCancelled)
{
[[NSNotificationCenter defaultCenter] postNotificationName:IAPZWBuyResultNotification object:self];
}
if (status == IAPZWDownOrderStarted) // 开始下单
{
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
}
else // 已经下单购买完成
{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
之后回调到下面的方法中
// 完成支付
-(void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(NSInteger)status
{
self.status = status;
if (transaction.error.code != SKErrorPaymentCancelled)
{
[[NSNotificationCenter defaultCenter] postNotificationName:IAPZWBuyResultNotification object:self];
}
if (status == IAPZWDownOrderStarted) // 开始下单
{
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
}
else // 已经下单购买完成
{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
3.支付后Code展示(重点在双重认证)
根据是否支付成功,若支付成功的话,开始双重认证
3.1先本地认证是否可以获得到receipt
- (void)completeAllDownOrderIsSuccess:(NSString *)productIdentifier {
NSData *receiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
NSString *receipt = [receiptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
[[DisplayHelper shareDisplayHelper]hideLoading:self.view];
if ([receipt length] > 0 && [productIdentifier length] > 0) {
/**
可以将receipt发给服务器进行购买验证
*/
[self sendRequestReceiptToAppStore:receipt];
}
// [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
3.2客户端去苹果服务器认证
-(void)sendRequestReceiptToAppStore:(NSString *)receipt
{
NSError *error;
NSDictionary *requestContents = @{@"receipt-data": receipt};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
if (!requestData) {
}else{
}
// NSURL *storeURL;
//#ifdef DEBUG
// storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
//#else
// storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
//#endif
NSURL *storeURL;
if (kIsProduction) {
storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
}else {
storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
}
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
WS(ws);
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
/* 处理error */
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse) { // 出错的情况
/* 处理error */
dispatch_async(dispatch_get_main_queue(), ^{
[DisplayHelper displayWarningAlert:@"支付失败!"];
});
}else{
/* 处理验证结果 */
if ([[jsonResponse allKeys]containsObject:@"status"]) {
NSString *statusStr =jsonResponse[@"status"];
if ([statusStr intValue] ==0) { // 成功后发服务器进行第二层验证
// 重发我们自己服务器的验证
[ws requestPayIsSuccess:receipt];
}else {
dispatch_async(dispatch_get_main_queue(), ^{
if ([statusStr intValue] ==21002) { // 请求过于频繁,有刷单嫌疑,需重发服务器
// 重发我们自己服务器的验证
[ws requestPayIsSuccess:receipt];
}else { // 其他失败的情况
[DisplayHelper displayWarningAlert:@"支付失败!"];
}
});
}
}
}
}
}];
}
3.3客户端让我们的服务端去苹果服务器认证
3.4服务端修改对应的商品字段,有必要的话,客户端也要进行修改
二.支付深度理解层次
1.区分应用内支付中内和外的关系
一般的微信支付宝支付时,是调用支付接口时直接跳转到对应的App,可以很明显的感觉到支付前后在App的内外关系,而对于苹果应用内支付呢,则必须区分开逻辑的内外,才算是真正的明白该支付的特点。
以本次的Demo为例:
当支付的输入输出框展示在您的面前时,就证明在应用外开始支付了,而当该框消失或者支付成功后如Demo中展示“支付成功!”时,就证明该进入应用内完成支付了!
所以处理好 支付:应用内到应用外;支付完成:应用外回调到应用内
2.商品列表请求和处理流程注意
2.1请求商品列表最好为接口通过接口返回而不是本地写死的情况
2.2支付过程中
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
中的状态为SKPaymentTransactionStatePurchased 时回调2次的区分为第一次为开始,第二次为成功请注意区分,本人当时就是爬的这里的坑造成的
2.3支付成功后不要忘了双重认证服务端
三.支付遇到的一些坑
1.双重校验的问题
什么是双重验证?
第一层认证为客户端和苹果那边认证是否支付成功,第二层认证为服务端和苹果那边认证是否支付成功
注意:客户端和苹果那边验证完成返回0为正常支付成功,但是返回status为21002时,官方解释是请求太频繁,有刷单嫌疑。故而这种情况也要把对应的数据发给服务端验证。
2.关于沙盒账户的使用
测试前需要先把AppStore中真实AppID给退出,之后再进行测试,避免调用失败;实际测试中发现一个沙盒账户只能绑定一个手机,请各位自己测试告知。
3.在类线上支付测试时充值问题
对真实AppID进行充值支付时需要验证以前的密码以及密保问题等
4.测试账号添加问题
测试账号添加时,密码不要设置过于简单,不然就会出现添加测试账号失败的问题;同样沙盒模式下登录前请退出以前的登录账户才能登录沙盒账户。另外沙盒模式验证完毕后请记得退出原来的沙盒账户,之后登录自己正式的账户,不然后续下载软件就会下载不了,总之,验证沙河模式后及时重新替换成自己的账户,避免来回使用麻烦。
5. SKPaymentTransactionStatePurchased 方法回调
下单完成后回调到此地时,一般会走二次,第一次为开始下订单;第二次为下订单成功的情况,详见Demo中区分情况。
四.手把手集成我的Demo
1.导入的类库及介绍
#import "IAPZWGoodsNoti.h"
#import "IAPZWGoodsManager.h"
#import <StoreKit/StoreKit.h>
2.Demo中类的介绍
IAPZWGoodsNoti 主要为获取商品列表的类
IAPZWGoodsManager 主要为支付过程中监听的帮助类
3.其他注意细节
按照以上的步骤执行后发现,咦,怎么还是无法支付成功呢?嘿嘿干货肯定要在最后展示哈!
3.1AppDeleage注册监听和取消监听问题
#pragma mark - UIApplication Methods
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Remove the observer
[[SKPaymentQueue defaultQueue] removeTransactionObserver: [IAPZWGoodsNoti sharedInstance]];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[UINavigationBar appearance]setBarTintColor:RGBCOLOR(129, 188, 53)];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
self.window =[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
[self initRootViewController];
[self.window makeKeyAndVisible];
#warning 记得调用初始化分享或支付SDK的代码
// // 初始化ShareSDK
// [self initSharedKey];
//
// // 初始化微信支付
// [self initWpay];
[[SKPaymentQueue defaultQueue] addTransactionObserver:[IAPZWGoodsNoti sharedInstance]];
return YES;
}
3.2如BoundId问题
更换 BoundId为您的可上线项目的BoundId
3.3是否是生产环境问题
全局搜索 kIsProduction 修改为0表示沙盒模式,为1为线上环境,注意!
3.4请求商品列表问题
从后台获取对应的商品列表之后再为下单处理,而不是本地写死,避免上线后无法更改。
3.5双重认证再次强调问题
保险起见,一定要做双重认证,另外返回status为21002时,也要发给后台进行是否支付成功的验证。
参考资料:
1.iOS之支付:https://www.jianshu.com/p/a9e17f50df9e
2.iOS开发内购全套图文教程:https://www.jianshu.com/p/86ac7d3b593a
3.iOS 内购支付两种模式:https://www.jianshu.com/p/15086493768a
4.谈谈苹果应用内支付(IAP)的坑:https://www.jianshu.com/p/c3c0ed5af309
5.【iOS】苹果IAP(内购)中沙盒账号使用注意事项:https://www.jianshu.com/p/1ef61a785508
最后附上:
2018最新研究应用内支付1:准备工作:https://www.jianshu.com/p/f090740991dd
最终的Demo地址:https://github.com/zxwIsCode/IAPZWDemo
最后期待下一篇,应用内支付上线注意的问题等