iap支付总结

最近应公司要求,集成iap支付,由于之前没做过,公司就我一个iOS,所以遇见不少坑,最后终于写完了,鉴于网上代码多的是缺胳膊少腿,所以我来一份全的,无废话的. 包括服务器验证,检验漏单的两种方式的.希望可以帮助到后续的伙伴们. 

In-App-Purchase

首先、 先熟悉一下整个iap支付的基本流程

1. 程序向服务器发送请求,获得一份产品列表。

2. 服务器返回包含产品标识符的列表。 // 就是获得产品ID < productID>

3. 程序向App Store发送请求,得到产品的信息。

4. App Store返回产品信息。

5. 程序把返回的产品信息显示给用户(App的store界面)

6. 用户选择某个产品

7. 程序向App Store发送支付请求

8. App Store处理支付请求并返回交易完成信息。

9. 程序从信息中获得数据,并发送至服务器。

10. 服务器纪录数据,并进行审(我们的)查。

11. 服务器将数据发给App Store来验证该交易的有效性。

12. App Store对收到的数据进行解析,返回该数据和说明其是否有效的标识。

13. 服务器读取返回的数据,确定用户购买的内容。

14. 服务器将购买的内容传递给程序。

 

其次、你需要在开发者中--Itunes Connect  ---我的App-----创建新应用----填写计费点,产品ID<功能-内部付费>

                               -- 账户与职能----创建沙盒测试账号                           // <由于这部分内容比较简单不赘述>                           

 

 

然后, 就是iOS端的代码开发了

第一步:

 

1、 在工程中引入 storekit.framework 和 #import <StoreKit/StoreKit.h>

 

2、 获得所有的付费Product ID列表。这个可以用常量存储在本地,也可以由自己的服务器返回。

 

 

代码: 因为我是接着别人的代码写,之前用ASIHttpRequest 进行网络请求,所以我也继承了,你们用AFN,就用AFN

#import "BPIAPPurchaseClass.h"

#import <StoreKit/StoreKit.h>

#import "ASIHTTPRequest.h"

#import "BPOrderInfo.h"

#import "MBProgressHUD.h"

 

@interface BPIAPPurchaseClass ()<SKProductsRequestDelegate,SKPaymentTransactionObserver,MBProgressHUDDelegate,ASIHTTPRequestDelegate,HttpRequestBaseDelegate>

{

    NSString* BPyInAppIdentifier;

    BPOrderInfo *orderInfoPlist;   // 订单Model

     UIActivityIndicatorView* spinner;

    NSArray *products;    // 商品Array

    NSArray *orderInfoArray;

    MBProgressHUD *hud;

    ASINetworkQueue *RequestQueue;

    

    id<HttpRequestBaseDelegate> delegate;

    SKPaymentTransaction *transactionList;

      NSString * money;

    SKProduct *skProduct;

     NSArray *productArray;

    NSDictionary *productDic;

}

 

@end

 

-(void)dealloc

{

     [orderInfoPlist release];

     [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; 

     [super dealloc];

 

}

 

- (instancetype)init

{

    self = [super init];

    if (self) {

        

        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    }

    return self;

}

 

+(BPIAPPurchaseClass *)SharedBPApplePayPlatform{

   static BPIAPPurchaseClass *applePayClass = nil;

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

       

        applePayClass = [[BPIAPPurchaseClass alloc]init];;

     

    });

    

    return applePayClass;

    

}

 

 // 加载菊花

-(void)showBPProgressHUD

{

        UIWindow* window = [UIApplication sharedApplication].keyWindow;

    if (!window)

    {

        window = [[UIApplication sharedApplication].windows lastObject];

    }

    NSLog(@"rootViewController = %@",window.rootViewController);

    

     hud = [[MBProgressHUD alloc]initWithView:window.rootViewController.view];

     hud.center = window.center;

     [window.rootViewController.view addSubview:hud];

     hud.color = [UIColor blackColor];

//   hud.alpha = 0.5;

     hud.delegate = self;

     hud.removeFromSuperViewOnHide = YES;

     hud.labelText = @"正在请求数据,请稍候";

     [hud show:YES];

     [hud release];

}

 

 

 

//请求数据,也可以直接在按钮里调用,传过来 product_id

-(void)ApplePurchaseWithProductInfo:(BPOrderInfo *)orderInfo

  {

    BPyInAppIdentifier = orderInfo.productId;

      orderInfoPlist = orderInfo;

  //      [self showBPProgressHUD];   //加载菊花打开

    NSArray *array = @[BPyInAppIdentifier];

    if ( [SKPaymentQueue canMakePayments]) {

 

        NSSet *set = [NSSet setWithArray:array];

       SKProductsRequest * appStoreRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];

         appStoreRequest.delegate = self; 

        [appStoreRequest start];

        NSLog(@"允许应用内支付");

        

    }else{

        

        NSLog(@"不允许应用内支付");

     

    }

 

}

 

//判断是否可以应用内支付

- (BOOL) canInAppPayment

{

    //你应该根据是否想让越狱的iOS设备允许内支付

    if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/Cydia.app"]){

        NSLog(@"越狱版设备!");

        return NO;

    }

    if ([SKPaymentQueue canMakePayments]) {

        return YES;

    } else {

        return NO;

    }

}

 

 

#pragma mark -SKProductsRequestDelegate 获取appstroe产品信息

-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response

{

    

   NSLog(@"@--------------收到appStore返回产品信息");

    NSArray *gameProduct = response.products;

      if (gameProduct.count == 0) {

        NSLog(@"无法获取产品信息,购买失败");

           return;

    }

 

    for (SKProduct *product in gameProduct) {

          money = [NSString stringWithFormat:@"%@",product.price];

           NSLog(@"@-------money = %@-------收到appStore返回产品信息",money);

        NSLog(@"产品id==%@",response.invalidProductIdentifiers);

        NSLog(@"产品数量==========%lu",(unsigned long)gameProduct.count);

        NSLog(@"产品标题 %@ ", product.localizedTitle);

        NSLog(@"产品描述信息: %@ ", product.localizedDescription);

        NSLog(@"价格: %@" , product.price);

        NSLog(@"Product id: %@ ", product.productIdentifier);

        SKMutablePayment *Mpayment = [SKMutablePayment paymentWithProduct:product];

 

        if ([[SKPaymentQueue defaultQueue] respondsToSelector:@selector(addPayment:)]) {

 

            [[SKPaymentQueue defaultQueue] addPayment:Mpayment];  //请求已经生效

            

            NSLog(@"请求生效");

        }

        

    }

 

    [request release];

 

}

 

- (void)completeTransaction:(SKPaymentTransaction *)transaction {

    

    NSLog(@"-------------------支付完成-----transaction=%@------------",transaction.transactionIdentifier);

//    [hud hide:YES];

    

    orderInfoPlist.orderId = transaction.transactionIdentifier;

    

   NSString * productIdentifier = transaction.payment.productIdentifier;

    if ([productIdentifier length]>0) {

        // 向自己的服务器验证购买凭证

     

        [self verifyReceiptFromCompanyServer:transaction];   //这个方法是内支付最重要的方法

  

    }

  

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

 

}

 

 

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error

{

    //处理错误

//   [hud hide:YES];

    [request cancel];

    request = nil;

}

 

 

 

// 交易失败,通知IAP进行UI刷新

- (void)failedTransaction:(SKPaymentTransaction *)transaction {

    

    if(transaction.error.code != SKErrorPaymentCancelled) {

        NSLog(@"购买失败");

     

        [BPCustomPromptBox showWithTitle:[BPLanguage getStringForKey:@"支付请求失效" InTable:@"BPMultiLanguage"] AndDisappearSecond:2];

 

   

    } else {

        NSLog(@"用户取消交易");

  

}

    

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

 

}

 

 

// 二次验证的过程

//向服务器请求求验证

// Send the Receipt Data to the App Store

#pragma amrk --- 注意iOS9的session请求

 

- (void)verifyReceiptFromCompanyServer:(SKPaymentTransaction *)transaction

{

      NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[[NSBundle mainBundle] appStoreReceiptURL]];//苹果推荐

    

    if (!urlRequest) { /* ... error ... */

        

        return;

    }

    

    NSURLSession *session = [NSURLSession sharedSession];

    

    NSURLSessionTask *task = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

        

//        NSLog(@"二次验证的data ====== %@",data);

        

         NSLog(@"这是iap支付的二次验证流程");

        

        

        NSString *base64String = [ASIHTTPRequest base64forData:data];

        

//        NSDictionary *receiptDictionary = @{@"receipt-data":base64String};

//

//         NSLog(@"二次验证的receiptDictionary ====== %@",receiptDictionary);

//

//        NSData *Receivedata = [NSJSONSerialization dataWithJSONObject:receiptDictionary options:NSJSONWritingPrettyPrinted error:&error];

        

        

//      NSLog(@"二次验证的提交验证的数据data ====== %@",Receivedata);

    

        if(!orderInfoPlist.callBackUrl)

        {

            orderInfoPlist.callBackUrl = @"0";

        }

       

        NSMutableDictionary *dicc = [NSMutableDictionary dictionary];

 

        [dicc setValue:base64String forKey:@"receipt"];

         [dicc setValue:money forKey:@"money"];

         [dicc setValue:[NSString stringWithFormat:@"%d",orderInfoPlist.ServerId] forKey:@"serverId"];

        [dicc setValue:version forKey:@"version"];

        [dicc setValue:[NSString stringWithFormat:@"%d",(int)[[NSDate date] timeIntervalSince1970]] forKey:@"time"];

        [dicc setValue:[BPUserPreferences CurrentChannelId] forKey:@"channelId"];

        [dicc setValue:transaction.transactionIdentifier  forKey:@"orderId"];

        

        

        NSString *str_sing = [BPUtility md5String:[NSString stringWithFormat:@"%@&key=jM2ilRIsu9dhDOwH",[self getHttpSing:dicc]]];

        

//        NSString *str = [self getHttpSing:dicc];

//        

//        NSLog(@"排序后的字符串 = %@",str);

        NSLog(@"dic.count = %ld",(unsigned long)dicc.count);

        

        

        

#pragma mark ----------- 交易信息本地化---------------------

        

        NSString *library = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];

        library = [library stringByAppendingFormat:@"/Caches"];

        NSFileManager *manger = [NSFileManager defaultManager];

        NSString  *path2 = [library stringByAppendingPathComponent:@"signReceipt"];

        NSString  *path3 = [library stringByAppendingPathComponent:@"orderInfo"];

        

          [manger createDirectoryAtPath:path2 withIntermediateDirectories:YES attributes:nil error:nil]; // 签名文件夹

        NSString *signPath = [NSString stringWithFormat:@"%@/%@",path2,@"sign.txt"];

        NSMutableArray *SignArray = [NSMutableArray array];

        [SignArray addObject:str_sing];

        [SignArray writeToFile:signPath  atomically:YES];

        NSLog(@"本地化签名信息path = %@",signPath);

    

        

        [manger createDirectoryAtPath:path3 withIntermediateDirectories:YES attributes:nil error:nil]; // 签名文件夹

        NSString *orderPath = [NSString stringWithFormat:@"%@/%@",path3,@"orderInfo.txt"];

        [dicc writeToFile:orderPath  atomically:YES];

        NSLog(@"本地化用户信息path = %@",orderPath);

        

 

        //  NSLog(@"dicc==== %@",signDic);

 

         BPHttpRequestBase *httpBase = [[BPHttpRequestBase alloc]init];

          ASIFormDataRequest *receiptRequest  = [httpBase CreateRequestWithUrl:@"服务器二次验证url" SetInfoTag:@"IapReceiptVerify"];

        httpBase.delegate = self;

        

        if (!receiptRequest)

        {

            [receiptRequest cancel];

            receiptRequest = nil;

        }

        

        [receiptRequest setRequestMethod:@"POST"];

        [receiptRequest addPostValue:@"webpay" forKey:@"action"];

           [receiptRequest addPostValue:@"ios" forKey:@"mode"];

        [receiptRequest addPostValue: str_sing forKey:@"sign"];

        [receiptRequest addPostValue:[NSString stringWithFormat:@"%d",(int)[[NSDate date] timeIntervalSince1970]] forKey:@"time"];

        [receiptRequest addPostValue:[BPUtility macaddress] forKey:@"mac"];

        [receiptRequest addPostValue:transaction.transactionIdentifier  forKey:@"orderId"];    //**订单号

        [receiptRequest addPostValue:@"verifyReceipt" forKey:@"method"];

        [receiptRequest addPostValue:money forKey:@"money"];     // 产品的钱数

        [receiptRequest addPostValue:base64String forKey:@"receipt"];

         receiptRequest.delegate = self;

        [httpBase.RequestQueue addOperation:receiptRequest];

        [httpBase.RequestQueue go];

        [receiptRequest startAsynchronous];   //异步请求序列

                

    }];

    

    [task resume];

    

    

}

 

 */

-(void)requestFinished:(ASIHTTPRequest *)request

{

 

    NSString *responseString = [request responseString];

    

    if (![request responseString]) {

        

        return ;

    }

    

    NSLog(@"request.url== %@-----二次验证结果----responseString= %@",request.url,responseString);

    

    NSDictionary *userInfo = request.userInfo;

    [BPQLoadingView hideWithAnimated:NO];

    

    if([[userInfo objectForKey:@"RequestTag"] isEqualToString:@"IapReceiptVerify"])

    {

        

        NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[request responseData] options:NSJSONReadingMutableContainers error:nil];

        

        NSLog(@"是否漏单服务器返回验证结果jsonDic = %@",dic);

        

        int response = [[dic objectForKey:@"result"] intValue];

        NSString *product_id = [dic objectForKey:@"product_id"];

        

        // iap二次验证成功,支付成功

        if(response == 0)

        {

            

         NSLog(@"支付验证成功,无漏单");

            

        }

        if(response == 21003)

        {

            NSLog(@"警告:出现漏单啦!!!");

            

//        BPOrderInfo *orderInfo = [[BPOrderInfo alloc]init];

//            orderInfo.productId = product_id;

//        [[BPIAPPurchaseClass SharedBPApplePayPlatform] ApplePurchaseWithProductInfo:orderInfo];

//        [orderInfo release];

            

            

            NSLog(@"漏单的产品ID = %@",product_id);

            

        }

        if (response == -1) {

            

            NSLog(@"支付签名错误");

            

        }if (response == -3){

            

            NSLog(@"订单已经到账");

        }

        

    }

    

}

 

 

#pragma mark - 购买商品

- (void)buyProduct:(SKProduct *)product

{

    // 1.创建票据

    SKPayment *payment = [SKPayment paymentWithProduct:product];

    

    // 2.将票据加入到交易队列中

    [[SKPaymentQueue defaultQueue] addPayment:payment];

 }

 

 

- (void)requestFailed:(ASIHTTPRequest *)request

{

    //出错处理

//    [hud hide:YES];

    

}

 

#pragma mark-SKPayment TransactionObserver支付结果

//----监听购买结果

//然后当用户输入正确的appStore帐号密码后进入(再次说明 如果是测试 必须是你注册的测试账号 不能使用真实的AppleID )

//方法在新交易被创建或更新时都会被调用

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions

{

    for (SKPaymentTransaction *transaction in transactions) {

            switch (transaction.transactionState) {

            case SKPaymentTransactionStatePurchased: //交易完成

                

                NSLog(@"交易完成transactionIdentifier= %@", transaction.transactionIdentifier);

                

                [self completeTransaction:transaction];

                

                break;

            case SKPaymentTransactionStateFailed://交易失败

                [self failedTransaction:transaction];

                

//                [BPCustomPromptBox showWithTitle:[BPLanguage getStringForKey:@"交易失败, 请重试" InTable:@"BPMultiLanguage"] AndDisappearSecond:2];

                NSLog(@"交易失败");

                

                break;

            case SKPaymentTransactionStateRestored://已经购买过该商品

                

                [self restoreTransaction:transaction];

                

                NSLog(@"已买过商品");

                break;

            case SKPaymentTransactionStatePurchasing: //商品添加进列表

                

                NSLog(@"商品添加进列表");

                

                break;

                

            default:

               

//             [hud hide:YES];

               

                break;

        }

        

        

    }

    

    

}

 

 

- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions

 

{

    NSLog(@"---------------移除商品-------------");

//     [hud hide:YES];

    

}

 

 

- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error

 

{

    

         NSLog(@"---------------重复支付失败-------------");

}

  

-(void)restoreTransaction: (SKPaymentTransaction *)transaction

{

    

    NSLog(@"---------------重复支付-----------------");

    // 对于已经购买的产品,恢复其处理逻辑

    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

    

}

 - (void)requestDidFinish:(SKRequest *)request {

    

    NSLog(@"请求结束");

    

//     [hud hide:YES];

    

 

}

 // 签名字典排序

- (NSString *)getHttpSing:(NSMutableDictionary *)dic

{

  

    NSString *str = nil;

      NSMutableArray *parameters_array = [NSMutableArray arrayWithArray:[dic allKeys]];

        [parameters_array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {

        return [obj1 compare:obj2];

        //return [obj2 compare:obj1];//降序

    } ];

    

    for (int i = 0; i<parameters_array.count; i++) {

        NSString *key = [parameters_array objectAtIndex: i];

        NSString * value = [dic objectForKey:key];

        if (i==0) {

            str = [NSString stringWithFormat:@"%@=%@",key,value] ;

            

        }else{

            

            str = [NSString stringWithFormat:@"%@&%@=%@",str,key,value];

        }

        

    }

    

    return str;

}

 

 楼主博客技术支持QQ: 1428542683

 

posted on 2016-01-19 13:36  在指尖跳舞的大狮子  阅读(1029)  评论(0编辑  收藏  举报

导航