iOS内购IAP(二) —— 工程实践(一)

版本记录

版本号 时间
V1.0 2017.05.24

前言

大家都知道,ios虚拟商品如宝石、金币等都需要走内购,和苹果三七分成,如果这类商品不走内购那么上不去架或者上架以后被发现而被下架。最近有一个项目需要增加内购支付功能,所以最近又重新集成并整理了下,希望对大家有所帮助。感兴趣的可以参考上面几篇。
1. iOS内购IAP(一) —— 基础配置篇(一)

实现详情

这里ios开发平台上的证书和税务等基础配置我就不说了,不明白的可以参考为的另外一篇文章ios内购之基础配置篇。这一篇的重点就是说一下我在项目中的具体实现,将抽象进一步具体化。

一、增加内购项目

在开发者账号itunesConnect--我的app中增加内购项目,如下图所示。

增加内购项目

细心的朋友可能发现,我这里状态中有的是已删除,有的是元数据丢失,这是怎么回事呢?这里我要和大家说一下,这里提示已删除的是因为我有的信息填错了,后来让我在这里删除了;而下面的元数据丢失是因为,在增加内购项目的时候,没有填写完全,比如screenshot我就没有填写,这里就显示了元数据丢失。这里还有一点需要提醒大家,产品ID是唯一的,假如你增加的内购项目删除了,那么这个产品ID就不能用了,你下边就是增加再多的内购项目也不能使用这个删除的产品ID了,所以填写要慎重。还要就是别忘记添加沙盒测试人员账号,不会的同样参考我上面的文章。还有就是这里的价格是根据内购的一个价格表定的,这里不支持自定义价格。


二、代码集成

下面我就只要说一下内购项目的代码及其逻辑。不废话,我就直接上代码了。

JJMoneyChargeVC.m文件

//增加头文件
#import "StoreKit/StoreKit.h"

//必要的几个属性
@property (nonatomic, strong) SKProductsRequest *productRequest;
@property (nonatomic, copy) NSString *productID;
@property (nonatomic, copy) NSString *chargeMoneyNumStr;
@property (nonatomic, copy) NSString *orderIDStr;

//开启内购
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.title = @"充值";
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self setupUI];
    //开启内购
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    
}

//下面就是具体的方法实现了

//点击手势选择要充值的金额
- (void)tapDidTapped:(UITapGestureRecognizer *)tapGesture
{
    switch (tapGesture.view.tag) {
            
        case JJChargeVCChargeType100:
            NSLog(@"我充值100金币");
            self.chargeMoneyNumStr = @"1";
            break;
            
        case  JJChargeVCChargeType600:
            NSLog(@"我充值600金币");
            self.chargeMoneyNumStr = @"6";
            break;
            
        case  JJChargeVCChargeType1800:
            NSLog(@"我充值1800金币");
            self.chargeMoneyNumStr = @"18";
            break;
            
        case  JJChargeVCChargeType3000:
            NSLog(@"我充值3000金币");
            self.chargeMoneyNumStr = @"30";
            break;
            
        case  JJChargeVCChargeType9800:
            NSLog(@"我充值9800金币");
            self.chargeMoneyNumStr = @"98";
            break;
            
        case  JJChargeVCChargeType19800:
            NSLog(@"我充值19800金币");
            self.chargeMoneyNumStr = @"198";
            break;
            
        default:
            break;
    }
    
    //向服务器请求生成订单号
    [self getOderNumbderRequest];
}

//请求服务器生成订单号
-(void)getOderNumbderRequest
{
    //先检查网络
    
    if (self.chargeMoneyNumStr.length == 0) {
        return;
    }
    NSMutableDictionary *paramatersDictM = [NSMutableDictionary dictionary];
    [paramatersDictM setObject:@"1"   forKey:@"chargeType"];
    [paramatersDictM setObject:self.chargeMoneyNumStr forKey:@"totalAmount"];
    
    [[JJNetWorkManager manager] requestByPostNetworkWithPayUrl:kappleInnerPharchase parameters:paramatersDictM success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"responseObject---%@",responseObject);
        if ([[responseObject objectForKey:@"code"] integerValue] == 0) {
            NSDictionary *dataDict = [responseObject objectForKey:@"data"];
            self.orderIDStr = [dataDict objectForKey:@"orderNo"];
            
            if ([self.chargeMoneyNumStr isEqualToString:@"1"]) {
                self.productID = @"这里是添加的内购项目产品ID";
            }
            
            else if([self.chargeMoneyNumStr isEqualToString:@"6"]){
                self.productID = @"这里是添加的内购项目产品ID";
            }
            
            else if([self.chargeMoneyNumStr isEqualToString:@"18"]){
                self.productID = @"这里是添加的内购项目产品ID";
                
            }
            
            else if([self.chargeMoneyNumStr isEqualToString:@"30"]){
                self.productID = @"这里是添加的内购项目产品ID";
                
            }
            
            else if([self.chargeMoneyNumStr isEqualToString:@"98"]){
                self.productID = @"这里是添加的内购项目产品ID";
                
            }
            
            else if([self.chargeMoneyNumStr isEqualToString:@"198"]){
                self.productID = @"这里是添加的内购项目产品ID";
            }
            
            if (self.productID && self.chargeMoneyNumStr) {
                  //根据产品信息发起请求,去苹果服务器请求商品
                [self beginRequestWithProductID:self.productID];
                }
            else {
                NSLog(@"不允许程序内付费,必须走内购");
            }
        }
    } error:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
         NSLog(@"error---%@",error);
    }];
}

//根据产品信息发起请求,去苹果服务器请求商品
- (void)beginRequestWithProductID:(NSString *)productID
{
    NSArray *productArr = [[NSArray alloc] initWithObjects:productID, nil];
    NSSet *productSet = [NSSet setWithArray:productArr];
    self.productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productSet];;
    self.productRequest.delegate = self;
    [self.productRequest start];
}

//下面就是走请求的代理方法
#pragma mark - SKProductsRequestDelegate

//当请求到可卖商品的结果会执行该方法
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    NSArray *productArr = response.products;
    
    if (productArr.count == 0) {
        NSLog(@"购买失败");
        return;
    }
    
    SKProduct *productInfo;
    for (SKProduct *product in productArr) {
        
        NSLog(@"商品描述 %@", [product description]);
        NSLog(@"商品标题 %@", [product localizedTitle]);
        NSLog(@"商品本地化描述 %@", [product localizedDescription]);
        NSLog(@"商品价格 %@", [product price]);
        NSLog(@"商品ID %@", [product productIdentifier]);
        
        if ([product.productIdentifier isEqualToString:self.productID]) {
            productInfo = product;
        }
    }
    
    //发送购买请求
    if (productInfo) {
        SKPayment *payment = [SKPayment paymentWithProduct:productInfo];
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }

}

//支付失败
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
    NSLog(@"%@---error",error);

}

//支付后的反馈信息
- (void)requestDidFinish:(SKRequest *)request
{
    //这里可以取消提示框
}

//下面就是查询购买结果
#pragma mark - SKPaymentTransactionObserver

//监听购买结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
                
            case SKPaymentTransactionStatePurchased:
                NSLog(@"可以请求购买了");
                [self completeTransactionWithTransaction:transaction];
                break;
                
            case SKPaymentTransactionStatePurchasing:
                NSLog(@"商品正在购买中。。。");
                break;
                
            case SKPaymentTransactionStateRestored:
                NSLog(@"已经购买过商品");
                [self restoreTransaction:transaction];
                break;
                
            case SKPaymentTransactionStateFailed:
                NSLog(@"购买失败");
                [self failedTransaction:transaction];
                break;
                
            default:
                break;
        }
    }

}

//完成购买
- (void)completeTransactionWithTransaction:(SKPaymentTransaction *)transaction
{
    [self getPhuechaseReceipt:transaction];
    
    NSString *productIdentify = transaction.payment.productIdentifier;
    if (productIdentify.length > 0) {
        //向自己的服务器请求验证,增加金币
      [self requestPurchaseViaServerWithReceipt:self.receiptStr];
    }
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

//服务器端验证金币,成功后服务器则给予用户虚拟物
- (void)requestPurchaseViaServerWithReceipt:(NSString *)storeReceipt
{
    NSMutableDictionary *mutableDictM = [NSMutableDictionary dictionary];
    [mutableDictM setObject:self.orderIDStr?:@"" forKey:@"orderNo"];
    [mutableDictM setObject:storeReceipt?:@"" forKey:@"receipt"];
    
    [[JJNetWorkManager manager] requestByPostNetworkWithPayUrl:kappleInnerPharchaseToMyServer parameters:mutableDictM success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"responseObject---%@",responseObject);
        if ([[responseObject objectForKey:@"code"] integerValue] == 0) {
            NSDictionary *dataDict = [responseObject objectForKey:@"data"];
            self.accountCashNumLabel.text = [NSString stringWithFormat:@"%@",[dataDict objectForKey:@"userCoin"]];
        }
    } error:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        
        NSLog(@"error--%@",error);
    }];
}

//已经购买过改商品
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
    NSString * productIdentifier = transaction.payment.productIdentifier;
    if ([productIdentifier length] > 0) {
        // 向自己的服务器验证购买凭证,成功后服务器则给予用户虚拟物
        //post请求传给服务器
        [self  requestPurchaseViaServerWithReceipt:self.receiptStr];
    }
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}

//购买失败
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
    if(transaction.error.code != SKErrorPaymentCancelled) {
        NSLog(@"购买失败");
        [MBProgressHUD hideHUDForView:self.view animated:YES];
        [self showNotice:@"购买失败"];
    }
    else {
        NSLog(@"用户取消交易");
        [MBProgressHUD hideHUDForView:self.view animated:YES];
        [self showNotice:@"交易已取消"];
    }
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

//获取购买凭证
-(void)getPhuechaseReceipt:(SKPaymentTransaction *)transaction
{
    //从沙盒中获取交易凭证并且拼接成请求体数据
    
    NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
    self.receiptStr = [receiptData base64EncodedStringWithOptions:0];
}


三、实现效果

ios内购的实现效果如下图所示。

确认购买
已购买
购买完成

我这里使用的是沙盒进行的测试。

后记

  内购就写这么多了,明后天有时间我再把支付宝支付的也加上。谢谢大家,晚安哦~~~

妈妈生日快乐!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • IAP内购支付流程 Client向Server发送请求,获得一份产品列表。 Server返回包含产品标识符的列表。...
    StevenHu_Sir阅读 27,643评论 10 50
  • 内购所需要的资料整理总结。引用大佬的经典文章:https://www.cnblogs.com/TheYouth/p...
    长衣貌阅读 2,492评论 2 13
  • 一、大变美女 以魔术的名义进行的游戏,化解女生的挑战。调节气氛,适合夜店和聚会。 详解: (在一些互动以后,女生向...
    脱单笔记阅读 787评论 0 0
  • 今天阅读课蓝芳老师的分享,她从非暴力沟通的表达与倾听两个方面,对整本书的内容进行了整理,让我对整本书的内容又有了一...
    hannah123123阅读 385评论 0 0
  • 昨天带女儿去医院看病,喉咙和咳嗽的原因要做雾化,昨天因时间紧,我竟然把药忘了拿,虽然在医院时总觉得好像漏了什么没做...
    昔日佳人阅读 120评论 0 0