iOS开发 app内购In-App Purchase(二)

原创 2016年08月31日 15:00:48

上篇的内购准备工作已完成,接下来就只需集成代码咯~

part 2. 代码

1.在项目中引入“storekit.framework”,并加入头文件#import <StoreKit/StoreKit.h>

2.在.h文件中加入“SKPaymentTransactionObserver ,SKProductsRequestDelegate”监听

//
//  LHYInAppPurchaseViewController.h
//  LHY
//
//  Created by scx on 16/2/28.
//  Copyright © 2016年 scx. All rights reserved.
//

#import "LHYDetailBaseViewController.h"
#import <StoreKit/StoreKit.h>


@interface LHYInAppPurchaseViewController : LHYDetailBaseViewController<SKPaymentTransactionObserver , SKProductsRequestDelegate>

@end

3.在.m文件

@implementation LHYInAppPurchaseViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.title = @"购买航币";
    
    //添加监听
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    self.productID = @"com.myTest.inappPurchase_prodect1";   
}

// app内购
- (void)toPay:(id)sender
{
    NSLog(@"____app内购 , %@" , self.productID);
    NSString *product = self.productID;
    if([SKPaymentQueue canMakePayments]){
        [self requestProductData:product];
    }else{
        [LHYAlertView showAlert:@"您的手机没有打开程序内付费购买"];
    }
    
}

//请求商品
- (void)requestProductData:(NSString *)type{
    
    NSLog(@"-------------请求对应的产品信息----------------");
    NSArray *product = [[NSArray alloc] initWithObjects:type, nil];
    
    NSSet *nsset = [NSSet setWithArray:product];
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
    request.delegate = self;
    [request start];
    
}
//收到产品返回信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    NSLog(@"--------------收到产品反馈消息---------------------");
    NSArray *product = response.products;
    if([product count] == 0){
        
        NSLog(@"--------------没有商品------------------");
        return;
    }
    
    NSLog(@"productID:%@", response.invalidProductIdentifiers);
    NSLog(@"产品付费数量:%lu",(unsigned long)[product count]);
    
    SKProduct *p = nil;
    for (SKProduct *pro in product) {
        NSLog(@"%@", [pro description]);
        NSLog(@"%@", [pro localizedTitle]);
        NSLog(@"%@", [pro localizedDescription]);
        NSLog(@"%@", [pro price]);
        NSLog(@"%@", [pro productIdentifier]);
        if([pro.productIdentifier isEqualToString:self.productID]){
            p = pro;
        }
    }
    
    SKPayment *payment = [SKPayment paymentWithProduct:p];
    
    LHYLog(@"发送购买请求");
    [[SKPaymentQueue defaultQueue] addPayment:payment];

}
//请求失败
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
    
    NSLog(@"------------------错误-----------------:%@", error);
}

- (void)requestDidFinish:(SKRequest *)request{
    NSLog(@"------------反馈信息结束-----------------");
}


//监听购买结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
//    [ZZZWaitingView hideInView:LHY_APPDELEGATE.window];
    for(SKPaymentTransaction *tran in transaction){
        switch (tran.transactionState) {
            case SKPaymentTransactionStatePurchased:{
                [ZZZWaitingView hideInView:LHY_APPDELEGATE.window];
                LHYLog(@"___%@" , tran.transactionIdentifier )
                [self completeTransaction:tran];
                LHYLog(@"交易完成");

            }
                break;
            case SKPaymentTransactionStatePurchasing:
                LHYLog(@"商品添加进列表");
    
                break;
            case SKPaymentTransactionStateRestored: {
                [ZZZWaitingView hideInView:LHY_APPDELEGATE.window];
                LHYLog(@"已经购买过商品");
                [self restoreTransaction:tran];
            }
                break;
            case SKPaymentTransactionStateFailed: {
                [ZZZWaitingView hideInView:LHY_APPDELEGATE.window];
                LHYLog(@"交易失败");
                [self failedTransaction:tran];
                UIAlertView *alerView2 =  [[UIAlertView alloc] initWithTitle:@"提示"
                                                                     message:@"购买失败,请重新尝试购买"
                                                                    delegate:nil cancelButtonTitle:NSLocalizedString(@"关闭",nil) otherButtonTitles:nil];
                
                [alerView2 show];
            }
                break;
            default:
                break;
        }
    }
}


//交易结束
- (void)completeTransaction: (SKPaymentTransaction *)transaction

{
    NSLog(@"-----completeTransaction--------");
   
    //系统IOS7.0以上获取支付验证凭证的方式应该改变,切验证返回的数据结构也不一样了。
    NSString *transactionReceiptString= nil;
    NSURLRequest *appstoreRequest = [NSURLRequest requestWithURL:[[NSBundle mainBundle]appStoreReceiptURL]];
    NSError *error = nil;
    NSData *receiptData = [NSURLConnection sendSynchronousRequest:appstoreRequest returningResponse:nil error:&error];
    transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    
    LHYLog(@"-----:%@" , transactionReceiptString);
    
    // NSString *str = [transactionReceiptString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    
    // 客户端验证
    [self verifyPruchase:transactionReceiptString];
    
    //向自己的服务器验证购买凭证
    [self verifyTransactionReceiptWithTRString:transactionReceiptString transaction:transaction];

    // Remove the transaction from the payment queue.
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    
}


当我们的交易完成后还要去appstore 上面去验证票据信息是否正确,这样我们才可以发放道具

关于验证 ---> apple官方文档


验证示例:

- (void)verifyPruchase:(NSString *)receiptStr
 {     
     // 发送网络POST请求,对购买凭据进行验证
     NSURL *url = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
     // 国内访问苹果服务器比较慢,timeoutInterval需要长一点
     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
     request.HTTPMethod = @"POST";
     
     // 在网络中传输数据,大多情况下是传输的字符串而不是二进制数据
     // 传输的是BASE64编码的字符串
     /**
      BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
      BASE64是可以编码和解码的
      */
     NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptStr];
     
     NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
     
     request.HTTPBody = payloadData;
     
     // 提交验证请求,并获得官方的验证JSON结果
     NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
     
     // 官方验证结果为空
     if (result == nil) {
         NSLog(@"验证失败");
     }
     
     NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingAllowFragments error:nil];
     
     NSLog(@"%@", dict);
     
     if (dict != nil) {
         // 比对字典中以下信息基本上可以保证数据安全
         // bundle_id&application_version&product_id&transaction_id
         NSString *status = LHY_OBJECT_STRING(dict[@"status"]);
         if ([status isEqualToString:@"0"]) {
             NSLog(@"验证成功");
         }else {
             NSLog(@"验证失败");
         }
     }
}


注意事项:

1. 沙盒环境测试app内购流程时,请使用未越狱的设备。

2. 请务必使用真机来测试,结果以真机为准。

3. 项目的Bundle identifier需要与您申请AppID时填写的bundleID一致,否则无法请求到商品信息。

4. 关于验证,苹果提供了两个验证链接,在审核时,苹果使用的是沙盒验证链接,沙盒验证链接,沙盒验证链接(重要的事情说三遍!),千万不要在程序中写死验证链接。

通常在验证时采用 可先验证沙盒,再验证正式 的方式,且最终验证放在服务端来完成。

沙盒验证:https://sandbox.itunes.apple.com/verifyReceipt

正式验证:https://buy.itunes.apple.com/verifyReceipt


- (void) failedTransaction: (SKPaymentTransaction *)transaction{
    NSLog(@"失败");
    if (transaction.error.code != SKErrorPaymentCancelled)
    {
        
    }
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
-(void)paymentQueueRestoreCompletedTransactionsFinished: (SKPaymentTransaction *)transaction{
    
}

- (void)restoreTransaction: (SKPaymentTransaction *)transaction
{
    NSLog(@" 交易恢复处理");
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
    
}

-(void) paymentQueue:(SKPaymentQueue *) paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error{
    NSLog(@"-------paymentQueue----");
}

- (void)dealloc{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}


补充:

在项目中测试中有遇到过当还未购买成功,用户就退出了界面的情况,这时客户端已经向苹果发出了购买请求,当再次进入会出现购买问题。

只需检测一下是否有未完成的交易即可。

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
    if (transactions.count > 0) {
        //检测是否有未完成的交易
        SKPaymentTransaction *transaction = [transactions firstObject];
        LHYToastView *toastView = [[LHYToastView alloc]init];
        if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
            [toastView setupWithText:[NSString stringWithFormat:@"您有一笔未完成的订单"]];
            [self completeTransaction:transaction];
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            
        }
        if (transaction.transactionState == SKPaymentTransactionStatePurchasing) {
            
        }
        if (transaction.transactionState == SKPaymentTransactionStateRestored) {
            [self restoreTransaction:transaction];
        }
        if (transaction.transactionState == SKPaymentTransactionStateFailed) {
            [self failedTransaction:transaction];
        }
        [LHY_APPDELEGATE.window addSubview:toastView];
    }
}

至此,app内购集成大功告成~

文章整理可能有疏漏,欢迎大家补充~

版权声明:本文为博主原创文章,未经博主允许不得转载。

System.Transactions.TransactionException: 该伙伴事务管理器已经禁止了它对远程/网络事务的支持。

近日在用wf做数据持久化时发现:该伙伴事务管理器已经禁止了它对远程/网络事务的支持。 (异常来自 HRESULT:0x8004D025)的错误。 {System.Transactions.Trans...

iOS开发之内购完全笔记(您已购买此 App 内购买项目。此项目将免费恢复。)

iOS开发之内购完全笔记 解决无限提示 --(您已购买此 App 内购买项目。此项目将免费恢复。)的问题 梳理内购逻辑 记录个人集成过程中遇到的问题点与大家分享...
  • jiisd
  • jiisd
  • 2016年01月16日 11:43
  • 23160

iOS 内付费(in-app purchase)--非消耗品的购买与恢复

iOS内付费的功能对于一个app来说是非常重要的,如果在这一环节出了一些致命的问题,那就很可能会影响app的推广和公司的利益了。我在很早之前写过一篇关于iOS内付费的文章(文章地址),在那篇博客中讲述...

iOS In-App Purchase 内购之 什么是恢复购买记录

Users restore transactions to maintain access to content they’ve already purchased. For example, whe...

iOS 内购IAP(In-App Purchases)代码实现(下)

上次介绍了苹果内购的交易流程,接下来讲讲获取票据信息和防止漏单。为什么要获取票据信息?...

iOS内购丢单处理及实现

源地址:http://www.aiuxian.com/article/p-1803307.html iOS内购实现及测试Check List 在内购使用中存在的几种丢单的情况 1.客户端...

iOS内购实现流程和丢单优化

客户端流程(这里只做个总结,网上有很多详细的介绍,这里就不多讲): 1.itc添加商品 2.itc添加沙盒测试账号 3.添加银行信息(这一步一定要有,不然调用支付会出现invalid pro...

iOS 工作之余总结 ---iOS内购完整流程以及踩过的坑!

公司最近的项目中,涉及到了直播打赏的功能。自然而然,iOS内购的功能是一定要有了(一次性消耗品只能用到内购,微信支付宝等支付是一定会被苹果拒的,与安卓开发不一样)。 下面,我会从头到尾的开始说...

iOS开发之内购完全笔记(您已购买此 App 内购买项目。此项目将免费恢复。)

1、内购流程 1、在 AppStore 中创建相应的物品,创建内购沙盒测试账号2、客户端从后台获取相应的物品 ID (当然也可以再客户端写死,但后期扩展性就受限制了)3、依据相应的物品 ID 请...

iOS开发内购教程In App Purchase 需要了解的

这一节里面主要介绍一些IAP的常见问题。 -我要在app里添加IAP,必须要注册自己的产品标识符(product identifiers)。产品标识符是什么? 产品标识符(Product Id...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:iOS开发 app内购In-App Purchase(二)
举报原因:
原因补充:

(最多只允许输入30个字)