苹果内购(订购为主)

苹果支付

一:绑定银行卡visa卡 参考链接

参考2

二:创建内购产品 :app里面的功能 参考链接

三:创建沙盒测试账户:Itunes里面用户和职能。参考链接

四:购买代码块 参考链接

+(instancetype)share{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        applePay = [[AHApplePay alloc]init];
    });
    if (applePay.myProducts.count == 0) {
        [applePay getPurchaseGoodsList];
    }
    return applePay;
}

//获取商品列表
-(void)getPurchaseGoodsList{
    if ([SKPaymentQueue canMakePayments]) {
        //此处内购商品编码是虚构的 ?????
        NSString *productPath = [[NSBundle mainBundle] pathForResource:@"productIDS" ofType:@"plist"];
        NSArray *products = [NSArray arrayWithContentsOfFile:productPath];
        [self getProductInfo:products];
    }else{
        if (_cannotPurchaseBlock) {
            _cannotPurchaseBlock(nil,@"失败,用户禁止应用内付费购买");
        }
    }
}

//购买商品
-(void)purchaseGoods:(id)goods{
    
    [self purchaseGoodsWithApplePurchase:goods];
}

//苹果支付
-(void)purchaseGoodsWithApplePurchase:(id)goods{
    NSString *identify = (NSString*)goods;
    SKProduct *paymentPay = nil;
    for (SKProduct *payment in self.myProducts) {
        if ([payment.productIdentifier isEqualToString:identify]) {
            paymentPay = payment;
        }
    }
    if (paymentPay == nil) {
        return;
    }
    //1.发起一个购买操作
    SKPayment *payment = [SKPayment paymentWithProduct:paymentPay];
    if (payment == nil) {
        return;
    }
    // 2.将票据加到到交易队列中
    [[SKPaymentQueue defaultQueue] addPayment:payment];
    //3.观察交易队列中交易发生的改变
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}

//购买 根据在plist文件位置
-(void)applePay:(NSInteger)idx{
    NSString *productPath = [[NSBundle mainBundle] pathForResource:@"productIDS" ofType:@"plist"];
    NSArray *products = [NSArray arrayWithContentsOfFile:productPath];
    if (idx<products.count) {
        NSString *identify = products[idx];
        [self.myProducts enumerateObjectsUsingBlock:^(SKProduct *payment, NSUInteger idx, BOOL * _Nonnull stop) {
            if ([payment.productIdentifier isEqualToString:identify]) {
                //1.发起一个购买操作
                SKPayment *paymentPro = [SKPayment paymentWithProduct:payment];
                // 2.将票据加到到交易队列中
                [[SKPaymentQueue defaultQueue] addPayment:paymentPro];
                //3.观察交易队列中交易发生的改变
                [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
                
            }
            
        }];
        
    }
    
}

//通过该IAP的Product ID向App Store查询,获取SKPayment实例,接着通过SKPaymentQueue的addPayment方法发起一个购买的操作
//下面的ProductId应该是事先在itunesConnect中添加好的,已存在的付费项目,否则会查询失败
-(void)getProductInfo:(NSArray *)productIds{
    NSSet *set = [NSSet setWithArray:productIds];
    _applePurchaseRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
    _applePurchaseRequest.delegate = self;
    [_applePurchaseRequest start];
}

#pragma mark - SKProductsRequestDelegate

//查询的回调函数 -----获取可销售的商品,并且排序
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    
    //获取到的所有内购商品
    NSArray *myProducts = response.products;
    //判断个数
    if (myProducts.count==0) {
        if (_failureBlock) {
            _failureBlock(nil,@"无法获取产品信息,购买失败。");
        }
        NSLog(@"无法获取产品信息,购买失败。");
        return;
    }else{
        self.myProducts = myProducts;
    }
    //刷新、配置UI ???
#warning 发起一个购买操作
    //        //1.发起一个购买操作
    //        SKPayment *payment = [SKPayment paymentWithProduct:myProducts[self.purchaseGoodsNumber]];
    //        // 2.将票据加到到交易队列中
    //        [[SKPaymentQueue defaultQueue] addPayment:payment];
    //        //3.观察交易队列中交易发生的改变
    //        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}

#pragma mark - SKPaymentTransactionObserver

//当用户购买的操作有结果时,就会触发下面的回调函数,相应进行处理
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
    
    for (SKPaymentTransaction *transaction in transactions) {
        
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:  //交易完成
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:     //交易失败
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:  //已经购买过该商品
                [self restoreTransaction:transaction];
                break;
            case SKPaymentTransactionStatePurchasing: //商品添加进列表
                LOG(@"商品添加进列表");
                break;
            default:
                break;
        }
    }
}

//交易完成后的操作
-(void)completeTransaction:(SKPaymentTransaction *)transaction{
    NSString * productIdentifier = transaction.payment.productIdentifier;
//    NSURL *url = [[NSBundle mainBundle] appStoreReceiptURL];
//    NSData *transactionReceiptData = [NSData dataWithContentsOfURL:url];
//    NSString *receipt = [transactionReceiptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
    
    NSData *data = transaction.transactionReceipt;
    NSString * receipt = [data base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];

    [[NSUserDefaults standardUserDefaults]setObject:receipt forKey:@"receipt"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    if ([productIdentifier length]>0 && !TestStringIsBlank(receipt)) {
        //向自己的服务器验证购买凭证????
        AHPersonInfoModel *infoModel = [AHPersonInfoManager share].infoModel;
        NSDictionary *dic = @{@"uid":@(infoModel.uid),@"token":infoModel.token,@"orderid":receipt};
        [AHRequestTool applepay:dic succussBlock:^(AHBaseModel *baseModel) {
            LOG(@"%@",baseModel);
            
            if (self.successfulBlock) {
                self.successfulBlock(nil, nil);
                self.successfulBlock = nil;
            }
        } failureBlock:^(NSString *errorStr) {
            if (self.failureBlock) {
                self.failureBlock(nil, nil);
                self.failureBlock  = nil;
            }
        }];
        //创建描述请求的JSON对象
        NSError *error;
        NSDictionary * requestContents = @{@"receipt-data":receipt,@"password":@"7ad47dcb6de94b08aa3ac3b7dd0d55f8",@"exclude-old-transactions":@(YES)};
        NSData * requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                               options:0 error:&error];
        if(!requestData){
            //错误处理
        }
        //创建带有收据数据的POST请求。
        NSURL * storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
        NSMutableURLRequest * storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
        [storeRequest setHTTPMethod:@"POST"];
        [storeRequest setHTTPBody:requestData];
        
        //在后台队列上连接到iTunes Store。
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
                               completionHandler:^(NSURLResponse * response,NSData * data,NSError * connectionError){
                                   if(connectionError){
                                       //错误处理
                                   } else {
                                       NSError *error;
                                       NSDictionary * jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
                                       if(jsonResponse){
                                           //验证
                                           LOG(@"%@",jsonResponse);
                                       }
                                   }}];

    }
    //移除transaction购买操作
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

//交易失败后的操作
-(void)failedTransaction:(SKPaymentTransaction *)transaction{
    
    if (transaction.error.code != SKErrorPaymentCancelled) {
        if (_failureBlock) {
            _failureBlock(nil,@"购买失败");
        }
        LOG(@"购买失败");
        
    }else{
        if (_failureBlock) {
            _failureBlock(nil,@"用户取消交易");
        }
        LOG(@"用户取消交易");
    }
    //移除transaction购买操作
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

//已经购买过该商品
-(void)restoreTransaction:(SKPaymentTransaction *)transaction{
    //对于已购买商品,处理恢复购买的逻辑
    //移除transaction购买操作
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

五:苹果支付验证代码块,建议后台验证。

NSString *recept =  [[NSUserDefaults standardUserDefaults]objectForKey:@"receipt"];
    if (!recept) {
        return;
    }
    //创建描述请求的JSON对象 只有订阅型才有密码设置。
    NSError *error;
    NSDictionary * requestContents = @{@"receipt-data":recept,@"password":@"7ad47dcb6de94b08aa3ac3b7dd0d55f8"};
    NSData * requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                           options:0 error:&error];
    if(!requestData){
        //错误处理
    }
    //创建带有收据数据的POST请求。
    NSURL * storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
    NSMutableURLRequest * storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:requestData];

    //在后台队列上连接到iTunes Store。
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
                           completionHandler:^(NSURLResponse * response,NSData * data,NSError * connectionError){
                               if(connectionError){
                                   //错误处理
                               } else {
                                   NSError *error;
                                   NSDictionary * jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
                                   if(jsonResponse){
                                       //验证
                                       LOG(@"%@",jsonResponse);
                                   }
                               }}];

内购沙盒测试续订周期时间不一样,且一天测试次数大概6次,且自动续费一般失败。

时间对应关系

六 :上线注意事项(这里是以订购为例)

购买协议连接或弹框一定要有
购买协议具体内容

购买协议具体内容:

一定要有:自动续费,可随时取消。提前24续费,下个周期第一天可取消。app设置里面可取消订购。协议里最好加上自己的公司。


NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:13],NSForegroundColorAttributeName:[UIColor blackColor]};
    NSString *contentStr = @"* The cost of subscription for Super Powers is 2.99 USD for 7 days, 7.99 USD for 1 month, 19.99 USD for 3 months, and 49.99 USD for 1 year.\n\n* If you choose to purchase Super Powers, the payment will be charged to your iTunes account at confirmation of purchase.\n\n* Your subscription automatically renews unless auto-renew is turned off at least 24-hours before the end of the current period.\n\n* Account will be charged for renewal within 24-hours prior to the end of the current period, and the cost of the renewal would be the same as your first payment.\n\n* You can manage your auto-renewal and auto-renewal may be turned off by going to your Account Settings after purchase.\n\n* No cancellation of the current subscription is allowed during the active subscription period.\n\n* If you don't want to purchase Super Powers, you can simply continue using Dittor for free.\n\n* Any unused portion of a free trial period, if offered, will be forfeited when you purchase a subscription to that publication.\n\nhttp://godittor.com/terms.html\n\nhttp://godittor.com/privacy.html";
    NSMutableAttributedString *contentAttr = [[NSMutableAttributedString alloc]initWithString:contentStr attributes:dic];
    NSRange range = [contentStr rangeOfString:@"http://godittor.com/terms.html\n\nhttp://godittor.com/privacy.html"];
    NSString *lin1 = @"http://godittor.com/terms.html";
    NSString *lin2 = @"http://godittor.com/privacy.html";
    NSRange range1 = [contentStr rangeOfString:lin1];
    NSRange range2 = [contentStr rangeOfString:lin2];
    self.range1 = range1;
    self.range2 = range2;
    [contentAttr addAttributes:@{NSForegroundColorAttributeName:[UIColor blueColor]} range:range];
    [contentAttr addAttributes:@{NSLinkAttributeName:lin1}
               range:range1];
    [contentAttr addAttributes:@{NSLinkAttributeName:lin2}
                         range:range2];
    self.textView.attributedText = contentAttr;
    
    self.textView.delegate =self;
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
{
    if (characterRange.location == self.range1.location && characterRange.length == self.range1.length) {
        [self bt_pushTrermAction:nil];
    }else if(characterRange.location == self.range2.location && characterRange.length == self.range2.length){
        [self bt_privacyAction:nil];
    }
    return NO;
}

app上线app描述包含商品名字,价格,最好加购买协议。

app上线的时候要在app描述里面加入内购信息

可参考爱奇艺,等有内购功能app在appstore上的描述。

爱奇艺

七 :深坑记录。

  • 第一次加内购或者有新商品,发布成功后,服务器并没有立刻同步,必须要等上一段时间(12小时左右)才能拉取到产品列表。
  • 点击发布后等待时间较久,大概12小时才能在appstore上搜索到。(官方说24之内可以看到)
  • 元数据需要,绑定银行卡,设置报税表,设置内购项目(完整的需要截图和描述),添加沙盒测试账号(沙盒账号不一定需要验证,沙盒账号iOS13在个人资料里有专门登陆的地方,iOS12在支付的时候会弹出登陆框登陆即可)。
  • 绑定银行卡的时候需要设置报税表,一般选美国就好,后面选择NO,然后同意条款,输入生日,签名就好。如下图。


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

推荐阅读更多精彩内容

  • 参考借鉴了一下几篇文章(在这里很感谢各位作者大大做出的总结和方案): http://blog.csdn.net/a...
    辉546阅读 13,903评论 3 12
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,790评论 25 707
  • 前言 本教程第一步是通过两个点画一条直线到图像中,例如 最后,通过画直线的API,画出一个头部模型 准备工作 现在...
    看到了是吧阅读 1,029评论 0 1
  • 秦攻宜陽,周君謂趙累曰:「子以為何如?」對曰:「宜陽必拔也。」君曰:「宜陽城方八里,材士十萬,粟支數年,公仲之軍二...
    止裳阅读 506评论 0 0
  • 最近人们对教养讨论的越来越多,教养到底体现在那些方面呢? 1、不对别人的事评头论足。别人做的好与坏都与...
    小调儿阅读 747评论 0 1