内购流程:
1. 用户先拿到购买产品的单子,
2. 拿着单子去苹果那里交钱,交完钱让苹果在单子上盖个章
3.拿着盖了章的单子传给自己的服务器来验证是否真的支付成功,服务器是跟苹果验证(我们客户端也是可以跟苹果验证的,只是这样安全性不高)
4.根据服务器返回的信息做具体的处理
先上代码干货
* 设置这个监听对象,会在该界面时时监测支付的状态变化
``` -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; // 添加观察者 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } -(void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; // 移除观察者 [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; } ```
* 点击某个商品,开始购买流程
#pragma Mark -- 内购功能模块 -(void)buyYuBiWithId:(NSString *)ProductID{ if([SKPaymentQueue canMakePayments]){ // productID就是你在创建购买项目时所填写的产品ID self.baseTableView.userInteractionEnabled = NO; selectProductID = [NSString stringWithFormat:@"%@",ProductID]; [self requestProduct]; }else{ _isBuying = NO; self.baseTableView.userInteractionEnabled = YES; DebugLog(@"不允许程序内付费"); UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"请先开启应用内付费购买功能。" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil]; [alertError show]; } } #pragma mark 1.请求所有的商品ID -(void)requestProduct{ // 1.拿到所有可卖商品的ID数组 NSSet *sets = [[NSSet alloc]initWithArray:_productIdArray]; // 2.向苹果发送请求,请求所有可买的商品 // 2.1.创建请求对象 SKProductsRequest *sKProductsRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:sets]; // 2.2.设置代理(在代理方法里面获取所有的可卖的商品) sKProductsRequest.delegate = self; // 2.3.开始请求 [sKProductsRequest start]; } #pragma mark 2.苹果那边的内购监听 -(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ NSArray *product = response.products; if([product count] == 0){ NSLog(@"没有商品"); return; } for (SKProduct *sKProduct in product) { NSLog(@"pro info"); NSLog(@"SKProduct 描述信息:%@", sKProduct.description); NSLog(@"localizedTitle 产品标题:%@", sKProduct.localizedTitle); NSLog(@"localizedDescription 产品描述信息:%@",sKProduct.localizedDescription); NSLog(@"price 价格:%@",sKProduct.price); NSLog(@"productIdentifier Product id:%@",sKProduct.productIdentifier); if([sKProduct.productIdentifier isEqualToString:selectProductID]){ [self buyProduct:sKProduct]; break; }else{ //NSLog(@"没有这个商品"); } } } #pragma mark 内购的代码调用 -(void)buyProduct:(SKProduct *)product{ // 1.创建票据 SKPayment *skpayment = [SKPayment paymentWithProduct:product]; // 2.将票据加入到交易队列 [[SKPaymentQueue defaultQueue] addPayment:skpayment]; }
* 下面这个方法就功能很多了
* 如果内购流程中的苹果支付完成,但是由于某些原因,没有结束该交易,苹果是会在页面出现的时候,回调这个交易的票据数据的,所以要有一个变量,标志SKPaymentTransactionStatePurchased是充值的时候,还是页面启动的时候回调的_isBuying
#pragma mark 4.实现观察者监听付钱的代理方法,只要交易发生变化就会走下面的方法 -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{ /* SKPaymentTransactionStatePurchasing, 正在购买 SKPaymentTransactionStatePurchased, 已经购买 SKPaymentTransactionStateFailed, 购买失败 SKPaymentTransactionStateRestored, 回复购买中 SKPaymentTransactionStateDeferred 交易还在队列里面,但最终状态还没有决定 */ for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchasing:{ NSLog(@"正在购买"); //存储购买的人ID [[NSUserDefaults standardUserDefaults] setObject:[UserModel uid] forKey:@"applePayMan"]; [[NSUserDefaults standardUserDefaults] synchronize]; }break; case SKPaymentTransactionStatePurchased:{ //[[SKPaymentQueue defaultQueue] finishTransaction:transaction]; if (_isBuying) {//主动发起的回调 //存储支付成功的单子 [[StroeObserver shareStoreObserver] saveApplePayHisTransactionId:transaction.transactionIdentifier andState:@"0" isUpdate:NO]; [self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction]; }else{//这是启动的回调 if (_dbArr.count>0) {//数据库有数据,判断是否有记录 //[SDIndicator showInfoWithMessage:@"数据库有数据"]; for (NSDictionary *dic in _dbArr) { if ([[dic objectForKey:@"transactionIdentifier"] isEqualToString:transaction.transactionIdentifier]) {//这是数据库中的单子 if ([[dic objectForKey:@"state"] isEqualToString:@"2"]) {//单子流程已经结束 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; //删除数据库中流程结束的单子 [[StroeObserver shareStoreObserver] deleteSuc:transaction.transactionIdentifier]; break; }else{//状态是0,1的都是支付成功了 //[SDIndicator showInfoWithMessage:@"数据库数据充值去"]; //购买后告诉交易队列,把这个成功的交易移除掉-- [queue finishTransaction:transaction] [self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction]; break; } } } } if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"applePayMan"] isEqualToString:[UserModel uid]]) { //[SDIndicator showInfoWithMessage:@"数据库无数据充值去"]; [self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction]; }else{ //[SDIndicator showInfoWithMessage:@"结束交易"]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } } }break; case SKPaymentTransactionStateFailed:{ [self hideHud]; NSLog(@"购买失败"); _isBuying = NO; self.baseTableView.userInteractionEnabled = YES; // 购买失败也要把这个交易移除掉 [queue finishTransaction:transaction]; }break; case SKPaymentTransactionStateRestored:{ [self hideHud]; _isBuying = NO; self.baseTableView.userInteractionEnabled = YES; NSLog(@"恢复购买中,也叫做已经购买"); // 恢复购买中也要把这个交易移除掉 [queue finishTransaction:transaction]; }break; case SKPaymentTransactionStateDeferred:{ NSLog(@"交易还在队列里面,但最终状态还没有决定"); }break; default: break; } } }
我们可以看到,使用数据库了原因是
* 在苹果返回票据,支付成功的时候,之后的流程,我们走的是自己的服务器去验证,这样的话,中间流程一旦中断,这个交易就可能没有结束,然后呢,苹果的监听,就会给回调回来之前支付成功但是并未结束交易的票据数组。
* 但是并不能直接调用充值接口,因为我支付成功,立马杀掉进程,重新启动,换个账号,进入充值界面的话,就会充值给另一个用户,所以需要判断这个交易的用户属性,采用了数据库保存,发起的订单,当然成功的单子就删除了【当然也有个问题,及时极限操作,苹果的支付票据还没回来,我就杀掉app,自然也就不能本地保存了,虽然没人这么做,所以存了一下,发起交易的人的uid,方便处理漏掉的单子的时候,不要给另外的人存了钱,自然最后剩余的情况就让找客服吧,无解了】
下面是要拿苹果返回的交易票据,转成base64去验证(客户端,服务器验证两种)
// 苹果内购支付成功 - (void)buyAppleStoreProductSucceedWithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp { NSString * productIdentifier = paymentTransactionp.payment.productIdentifier; DebugLog(@"productIdentifier Product id:%@", productIdentifier); NSString *transactionReceiptString= nil; //系统IOS7.0以上获取支付验证凭证的方式应该改变,切验证返回的数据结构也不一样了。 NSString *version = [UIDevice currentDevice].systemVersion; if([version intValue] >= 7.0){ // 验证凭据,获取到苹果返回的交易凭据 // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址 NSURLRequest * appstoreRequest = [NSURLRequest requestWithURL:[[NSBundle mainBundle]appStoreReceiptURL]]; NSError *error = nil; NSData * receiptData = [NSURLConnection sendSynchronousRequest:appstoreRequest returningResponse:nil error:&error]; transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; }else{ NSData * receiptData = paymentTransactionp.transactionReceipt; // transactionReceiptString = [receiptData base64EncodedString]; transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; } // 去验证是否真正的支付成功了 [self checkAppStorePayResultWithBase64String:transactionReceiptString WithPaymentTransactionp:paymentTransactionp]; }
-
下面方法,接口调用成功的话,因为状态值是0的话,原因是已经充值成功,或者票据不对,这两种情况也必须直接结束交易,所以只要接口调用成功,一样结束交易
-
接口调用失败的话,再次调用,直到成功
- (void)checkAppStorePayResultWithBase64String:(NSString *)base64String WithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp{ NSMutableDictionary *prgam = [[NSMutableDictionary alloc] init]; [prgam setValue:base64String forKey:@"receipt-data"]; __weak typeof(self)WS = self; [Api requestWithMethod:nil withPath:API_URL_Apple_pay withParams:prgam withSuccess:^(id responseObject) { [WS hideHud]; _isBuying = NO; WS.baseTableView.userInteractionEnabled = YES; //返回1成功充值,返回0,该单已经充值过了,没结束交易 或者票据错误---都结束交易 //更新充值成功的单子 [[StroeObserver shareStoreObserver] saveApplePayHisTransactionId:paymentTransactionp.transactionIdentifier andState:@"2" isUpdate:YES]; //结束苹果pay的交易队列,不然会出现--(您已购买此 App 内购买项目。此项目将免费恢复)提示 [[SKPaymentQueue defaultQueue] finishTransaction:paymentTransactionp]; //删除数据库中流程结束的单子 [[StroeObserver shareStoreObserver] deleteSuc:paymentTransactionp.transactionIdentifier]; if ([responseObject[@"status"] integerValue] == 1) { if(_giftRequestBlock){ WS.giftRequestBlock(@"1"); } [SDIndicator showSuccessWithMessage:@"充值成功"]; [WS getData]; } } withError:^(NSError *error) { _isBuying = YES; WS.baseTableView.userInteractionEnabled = NO; [WS checkAppStorePayResultWithBase64String:base64String WithPaymentTransactionp:paymentTransactionp]; }];
下面在说说,数据库存储的逻辑
#import <UIKit/UIKit.h> #import "StroeObserver.h" @implementation StroeObserver + (StroeObserver *)shareStoreObserver { static StroeObserver *_storeOb = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _storeOb = [[StroeObserver alloc] init]; }); return _storeOb; } -(void)saveApplePayHisTransactionId:(NSString *)transactionId andState:(NSString *)state isUpdate:(BOOL)isUpdate { dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ /*将数据存入数据库*/ /*打开数据库*/ DB_INIT(db); NSString *str = DB_PATH; DebugLog("%@",str); DB_OPEN; /*创建数据库*/ NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID]; //字段有:transactionIdentifier、productIdentifier、uid、transactionDate(支付时间)、rechargeDate(充值到账时间)、state([未支付]「已支付」和「已充值」) NSString *tableSql = [NSString stringWithFormat:@"create table if not exists %@(transactionIdentifier,state,uid)",tableName]; DB_CREATE(tableSql) BOOL INSERT; if (isUpdate) { INSERT = [db executeUpdate:[NSString stringWithFormat:@"update %@ set state = ? where transactionIdentifier = ?",tableName],state,transactionId]; }else{ INSERT = [db executeUpdate:[NSString stringWithFormat:@"insert into %@ values(?,?,?)",tableName],transactionId,state,[UserModel uid]]; } DB_INSERT; DB_CLOSE; }); } //删除成功的交易 -(void)deleteSuc:(NSString*)transactionIdentifier{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ DB_INIT(db); DB_OPEN; NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID]; BOOL ret = [db executeUpdate:[NSString stringWithFormat:@"delete from %@ where transactionIdentifier='%@'", tableName,transactionIdentifier]]; if (!ret) { DebugLog(@"删除失败"); }else{ DebugLog(@"删除成功"); } DB_CLOSE; }); } //获取数据库中的支付成功,充值不成功订单 - (void)initApplePayHisDataFromDB { _dataArray = [[NSMutableArray alloc]init]; dispatch_queue_t queue = dispatch_queue_create("initApplePayHisDataFromDB", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ /*读取数据库旧数据*/ DB_INIT(db); DB_OPEN; NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID]; FMResultSet *rs = [db executeQuery:[NSString stringWithFormat:@"select * from %@ where uid=?",tableName],SWUID]; while ([rs next]) { NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys: [rs stringForColumn:@"state"],@"state", [rs stringForColumn:@"uid"],@"uid", [rs stringForColumn:@"transactionIdentifier"],@"transactionIdentifier",nil]; [_dataArray addObject:dic]; } DB_CLOSE; }); } @end
就看效果了。。。
BYZqk
[https://www.jianshu.com/u/c6866a2b7547]
2018-04-2516:10:44