• 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

  • 相关阅读:
    MongoDB的Spring-data-mongodb集成(Win10 x64) 第一章
    Linux 环境部署记录(三)
    onsubmit不起作用的原因
    sql server 将时间中的时分秒改为00:00:00
    js文件被浏览器缓存
    Action<>和Func<> 区别
    sql2008 express 实现自动备份
    Centos 7 无法上网的解决办法
    js 中的[] {}是什么意思
    js中var a={}什么意思
  • 原文地址:https://www.cnblogs.com/fan-cong/p/5141980.html
Copyright © 2020-2023  润新知