iOS之苹果内购最新2017年教程

公司有个功能叫付费加速制作模型。这属于服务费,之前用的支付宝和微信支付,后来微信通过其官方公众号发出通知,称因为苹果对iOS应用的新规定,将关闭微信公众号在iOS上的打赏功能。所以为了不被苹果拒绝。支付换做内购了。今天学习下如何使用苹果支付。

协议,税务和银行业务

1242012-69099549f1c19d98.png

点击

申请合同.png

点击request申请合同

1242012-d240abc2ad52cf23.png

添加联系人

1242012-e4b46eebc037c1c9.png

同意协议然后提交


1242012-447905cbad7e757b.png

设置相关的信息如联系人,银行,税务信息。

1242012-56d222c1b8dd3aa8.png

联系人信息

先点击蓝色的add New Contact 写完后save,注意在role(角色)中,为联系人选择身份(高级管理人员 ,金融,技术,法律,营销),点击Done保存

银行信息

添加银行信息

1242012-f0975f1235ee8cfa.png

选择国家


1242012-7222d2b96d4c8463.png

填写CNAPS

CNAPS 银行网点联行号.我们可以直接到银行客服电话查询 CNAPS, 快速便捷.还可以通过下图的Look up CNAPS Code 查询. 不过有的查询不到


1242012-52e42c1d0678e05a.png
1242012-f32b0ee23bb4eb3c.png

确认银行信息


1242012-407a7da27f6730a1.png

.填写公司银行账号信息


1242012-efc941edf1b339fc.png

税务信息

税务信息有三个选项:美国税务、澳大利亚税务、加拿大税务。我们在这里选择美国税务就可以

1242012-2eec17fd00430f79.png
1242012-e7cbd0888d850872.png

选No

1242012-391d636a208ade83.png

选No

填写税务相关信息

9b.Foreign TIN 是填写公司税务识别码.公司营业执照上同意社会信用代码去掉前两位和最后一位就是公司税务识别码(15位)


1242012-eeb0455ed5164c73.png

点击提交之后会让你确认信息,确认无误后再次点击提交(提交后无法修改)

添加内购

点击功能,然后点加号。


WX20170503-155202@2x.png

根据项目需求,选择内购项目

1242012-ee6d6740f12728a9.png

填写内购项目的相关信息

商品名称根据你的消费道具的实际意义来说明
产品ID是比较重要的,只要唯一即可,在实际应用中,一定要认真填写。
选择价格

WX20170503-155643@2x.png

本地化版本

将在 App Store 上可见的 App 内购买项目名称。可以参考App Store中其他应用的内购项目描述
描述不得少于10个字符


WX20170503-155755@2x.png

审核信息

1.只会在审核中使用屏幕快照,不会将其显示在 App Store 上。屏幕快照必须具有适合您 App 平台的有效尺寸。
2.能够有助于我们进行审核的关于您的 App 内购买项目的其他信息,如测试帐户(包括用户名、密码等)。审核备注不得超过 4000 个字符。


WX20170503-155814@2x.png

内购项目则添加完成

申请沙盒测试账号(用来测试购买项目)

我们不必用真实的人民币测试购买内购项目
首先我们回到iTunes Connect中,在这里我们选择用户和职能。
添加沙箱技术测试员

1242012-e90d8ee77aa59add.png

添加测试员详细信息

所有信息都可以随意填写,不用管是否真实。但是App Store地区必须对应
注意: 邮件地址不能填写与 Apple ID 关联的, 密码保护问题必须在 6 到 35 个字符之间。密码必须包含至少一个大写字母。

1242012-7a721315d3c40521.png
WX20170503-160340@2x.png

内购原理

20150723155327761.jpeg
  • 先学习下内购的流程吧。了解了流程代码就简单了。首先app先去苹果服务器请求产品信息,然后拿着请求下来的产品信息去发送内购请求就是步骤1。
  • 然后苹果服务器返回支付的结果receipt_data给app,这receipt_data是交易凭据这是步骤2.为了防止黑客盗刷。所以得拿receipt_data来做验证,看看到底是不是支付了。
  • app把receipt_data传给咱们自己的服务器这是步骤3,
  • 服务器把data转码后这是步骤4。
  • 咱们的服务器去苹果那里验证是不是付款了。这是步骤5.
  • 苹果服务器返回验证的结果给咱们的服务器。这是步骤6.
  • 咱们的服务器把最后的验证结果给app,app做出一些动作。这是步骤7.

在项目中实现购买

#import "PurchaseService.h"
#import <StoreKit/StoreKit.h>
#import "MaskView.h"

@interface PurchaseService()<SKPaymentTransactionObserver,SKProductsRequestDelegate>
//productId是在开发者平台填写的产品id,排队加速
@property(nonatomic,copy)NSString *productId;

@property(nonatomic,strong)MaskView *maskView;

@property(nonatomic,assign)BOOL isAccelerate;

@property(nonatomic,strong)NSString *filePath;

@end

@implementation PurchaseService

- (instancetype)initWithAccelerate:(BOOL)isAccelerate;

{
    self = [super init];
    if (self) {
       
        self.isAccelerate = isAccelerate;
       
        
    }
    return self;
}

- (void)setIsAccelerate:(BOOL)isAccelerate
{
    _isAccelerate = isAccelerate;
     NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    //槽位的路径
    NSString *receiptPath = [docPath stringByAppendingPathComponent:@"slot_receitp.plist"];
    
    if (_isAccelerate == YES) {
        
        receiptPath = [docPath stringByAppendingPathComponent:@"accelerate_receitp.plist"];
    }
    self.filePath = receiptPath;
}

- (void)pay
{
  
    NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:self.filePath];
    
    if (dic  != nil) {
        
        DLog(@"沙盒中拿");
        
        NSString *receiptEncodeStr = dic[@"receiptEncodeStr"];
        //去服务器验证
        //配置服务器需要的参数
        NSMutableDictionary *params = [NSMutableDictionary dictionary];
        params[@"xxx"] = xxx;
        //params[@"xxx"] = @(1x);
        params[@"xxx"] = xxxx;
        params[@"xxx"] = @(false);
        [self verifyPurchaseWithDic:params];
        
        return;
    }
    
    //添加一个交易队列观察者
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    
    //判断是否允许程序内购买
    if ([SKPaymentQueue canMakePayments]) {
        
        //获取商品列表
        WS(weakSelf)
        [SWAYNetWorking get:IapProductInfoUrl parameters:nil success:^(id responseObject) {
            
            if ([responseObject[@"code"] integerValue] == 100) {
               
                NSDictionary *dic = [responseObject[@"data"] objectAtIndex:0];
                if (self.isAccelerate == NO) {
                    dic = [responseObject[@"data"] objectAtIndex:1];
                }
                NSInteger p_id = [dic[@"productId"] integerValue];
                weakSelf.productId =  [NSString stringWithFormat:@"%ld",p_id];
                //去苹果服务器请求产品信息
                [weakSelf requestProductData:weakSelf.productId];
            }
            
        } failure:^(NSError *error) {
            
        }];
        
        
    }else{
        
        [SVProgressHUD showImage:nil status:@"用户禁止应用内购买,请到设置->通用->访问限制里打开App内购买项目"];
        
    }
    
}


// 去苹果服务器请求产品信息
- (void)requestProductData:(NSString *)productId {
    
    self.maskView = [[MaskView alloc]init];
    [KWindow addSubview:self.maskView];
    [self.maskView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.bottom.top.mas_equalTo(0);
    }];
    
    
    
    NSArray *productArr = [[NSArray alloc]initWithObjects:productId, nil];
    
    NSSet *productSet = [NSSet setWithArray:productArr];
    
    SKProductsRequest *request = [[SKProductsRequest alloc]initWithProductIdentifiers:productSet];
    request.delegate = self;
    [request start];
    
}

// SKProductsRequestDelegate 收到产品返回信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    
    
    NSArray *productArr = response.products;
    
    if ([productArr count] == 0) {
        
        DLog(@"没有该商品");
        [self.maskView removeFromSuperview];
        return;
    }
    
    DLog(@"productId = %@",response.invalidProductIdentifiers);
    DLog(@"产品付费数量 = %zd",productArr.count);
    
    SKProduct *p = nil;
    
    for (SKProduct *pro in productArr) {
        DLog(@"description:%@",[pro description]);
        DLog(@"localizedTitle:%@",[pro localizedTitle]);
        DLog(@"localizedDescription:%@",[pro localizedDescription]);
        DLog(@"price:%@",[pro price]);
        DLog(@"productIdentifier:%@",[pro productIdentifier]);
        if ([pro.productIdentifier isEqualToString:self.productId]) {
            p = pro;
        }
    }
    
    SKPayment *payment = [SKPayment paymentWithProduct:p];
    
    //发送内购请求
    [[SKPaymentQueue defaultQueue] addPayment:payment];
    
}

- (void)requestDidFinish:(SKRequest *)request {
    NSLog(@"完成");
    [self.maskView removeFromSuperview];
}



- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
    NSString *errMessage = [error.userInfo objectForKey:@"NSLocalizedDescription"];
    [SVProgressHUD showErrorWithStatus:errMessage];
    [self.maskView removeFromSuperview];
    
}


// SKPaymentTransactionObserver监听购买结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
    
    for (SKPaymentTransaction *tran in transactions) {
        switch (tran.transactionState) {
            case SKPaymentTransactionStatePurchased: //交易完成
                // 发送到服务器验证凭证
                [self completeTransaction:tran];
                [[SKPaymentQueue defaultQueue]finishTransaction:tran];
                break;
            case SKPaymentTransactionStatePurchasing: //商品添加进列表
                
                break;
            case SKPaymentTransactionStateRestored: //购买过
                // 发送到苹果服务器验证凭证
                [[SKPaymentQueue defaultQueue]finishTransaction:tran];
                break;
            case SKPaymentTransactionStateFailed: //交易失败
                
                [[SKPaymentQueue defaultQueue]finishTransaction:tran];
                [SVProgressHUD showErrorWithStatus:@"购买失败"];
                break;
                
            default:
                break;
        }
    }
}

- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
    //商品id
    NSString *produckID = transaction.payment.productIdentifier;
    //订单号
    NSString *transactionIdentifier = transaction.transactionIdentifier;
    
    // 验证凭据,获取到苹果返回的交易凭据
    // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    // 从沙盒中获取到购买凭据
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
    NSString *receiptEncodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    
    //存储收据
    NSMutableDictionary *receiptDic = [NSMutableDictionary dictionary];
    [receiptDic setObject:produckID forKey:@"produckID"];
    [receiptDic setObject:transactionIdentifier forKey:@"transactionIdentifier"];
    [receiptDic setObject:receiptEncodeStr forKey:@"receiptEncodeStr"];
    
   
    [receiptDic writeToFile:self.filePath atomically:YES];
    //配置服务器需要的参数
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    params[@"receipt"] = receiptEncodeStr;
    params[@"chooseEnv"] = @(false);
    [self verifyPurchaseWithDic:params];
    
}

#pragma mark-  服务器端做验证
- (void)verifyPurchaseWithDic:(NSDictionary *)dic
{
    [NetWorking post:IapCertificateUrl parameters:dic success:^(id responseObject) {
        
        if ([responseObject[@"code"] integerValue] == 100) {
            
            //刷新首页
             [[NSNotificationCenter defaultCenter] postNotificationName:@"reloadHomeNotification" object:nil];
            
            [SVProgressHUD showImage:nil status:responseObject[@"message"]];
    
            [[NSFileManager defaultManager] removeItemAtPath:self.filePath error:nil];
            
            
        }else{
            
            [SVProgressHUD showImage:nil status:responseObject[@"message"]];
            
            
        }
        
    } failure:^(NSError *error) {
        
    }];
}


//沙盒测试环境验证
#define SANDBOX @"https://sandbox.itunes.apple.com/verifyReceipt"
//正式环境验证
#define AppStore @"https://buy.itunes.apple.com/verifyReceipt"
// 验证购买
- (void)verifyPurchaseWithPaymentTrasaction {
    
    // 验证凭据,获取到苹果返回的交易凭据
    // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    // 从沙盒中获取到购买凭据
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
    
    
    // 发送网络POST请求,对购买凭据进行验证
    //测试验证地址:https://sandbox.itunes.apple.com/verifyReceipt
    //正式验证地址:https://buy.itunes.apple.com/verifyReceipt
    NSURL *url = [NSURL URLWithString:SANDBOX];
    NSMutableURLRequest *urlRequest =
    [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
    urlRequest.HTTPMethod = @"POST";
    NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
    NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
    urlRequest.HTTPBody = payloadData;
    // 提交验证请求,并获得官方的验证JSON结果 iOS9后更改了另外的一个方法
    NSData *result = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:nil];
    // 官方验证结果为空
    if (result == nil) {
        NSLog(@"验证失败");
        return;
    }
    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingAllowFragments error:nil];
    if (dict != nil) {
        // 比对字典中以下信息基本上可以保证数据安全
        // bundle_id , application_version , product_id , transaction_id
        //        NSLog(@"验证成功!购买的商品是:%@", @"_productName");
        
        NSLog(@"验证成功%@",dict);
    }
    
}

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




@end

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

推荐阅读更多精彩内容

  • 公司项目中有虚拟产品,所以要使用苹果内购.自此记录一下苹果内购的流程.前提是已有开发者账号 协议,税务和银行业务 ...
    看我的大白眼阅读 14,988评论 62 70
  • 公司项目中有虚拟产品,所以要使用苹果内购.自此记录一下苹果内购的流程.前提是已有开发者账号 协议,税务和银行业务 ...
    better栋阅读 2,002评论 0 7
  • 转载 公司项目中有虚拟产品,所以要使用苹果内购.自此记录一下苹果内购的流程.前提是已有开发者账号 协议,税务和银行...
    George2016阅读 1,287评论 9 6
  • 我躺在草原上,周围芳草离离,边上一匹马,它看起来健硕无比,倚在广袤的草原上,一阵清风吹过,长鬓飞扬,犹如...
    几花阅读 508评论 0 0
  • 古老的岁月,有着一尘不染的天空,在哪个充满欢乐的春天,忧伤以及空空荡荡;我伸出双手,丢掉了什么又抓住些什么。在寒冷...
    022dc1d12265阅读 348评论 1 0