【iOS】苹果IPA(内购)

一、内购大体流程

1、苹果开发者后台信息配置:银行信息、协议、税务信息填写。
2、内购商品添加。
3、沙盒测试帐号添加。
4、代码实现。
5、内购测试。
6、内购注意事项。

二、银行信息、协议、税务信息填写

2-1、进入iTunes Connect,设置协议、税务和银行业务 信息。

【iOS】苹果IPA(内购)_第1张图片
协议、税务和银行业务入口

2-2、申请合同

合同类型分为三种:
iOS Free Application(免费应用合同)
iOS Paid Application(付费应用合同)
iAd App NetNetwork(广告合同)

【iOS】苹果IPA(内购)_第2张图片
协议、税务和银行业务

这里主要记录一下iOS Paid Application(付费应用合同)的申请。

2-3、Contact Info(填写联系方式)

需要填写五个职务对应的人员信息,如果是独立开发者可以全部填写自己。
Senior Management:高管
Financial:财务
Technical:技术支持
Legal:法务
Marketing:市场推广

2-4、Bank Info(银行信息)

选择添加银行,如果没有添加过银行,则需要新增银行信息。


【iOS】苹果IPA(内购)_第3张图片
银行信息

1)选择银行所在国家


【iOS】苹果IPA(内购)_第4张图片
选择银行所在国家

2)填写银行标识CNAPS Code
可以自行百度,依据三个关键信息查询:
Bank Name:银行的英文名称(不能是拼音)
City:银行所在的城市英文名称(中国的城市用拼音)
Postal Code:邮编


【iOS】苹果IPA(内购)_第5张图片
填写银行标识CNAPS Code

3)填写完之后核对银行信息,点击next,填写银行信息,并确认
Bank Account Number:银行账号
Confirm Bank Account Number:再次输入银行账号
Account Holder Name:持卡人姓名,中文名用拼写,名在前,姓在后
Bank Account Currency:货币类型,一般国内的开发者选择CNY

2-5、填写税务信息
1)税务信息分为三种:
U.S Tax Forms:美国税务
Australia Tax Forms:澳大利亚税务
Canada Tax Forms:加拿大税务


【iOS】苹果IPA(内购)_第6张图片
填写税务信息

美国税务是必填的,其他两个根据自身情况填写。
2)一堆需要做出选择的东西

首先问你"你是一个美国公民,美国居民,美国伙伴关系,或美国公司?"根据实际情况选择,然后submit


【iOS】苹果IPA(内购)_第7张图片
1.png

第二个问题“有没有在美国的商业性活动?” ,同样根据实际情况选择,submit

【iOS】苹果IPA(内购)_第8张图片
2.png

然后填写你的税务信息,包括以下几点:
Individual or Organization Name:个人或者组织名称
Country of incorporation: 所在国家
Type of Beneficial Owner:受益方式,独立开发者选个人
Permanent Residence:居住地址
Mailing address:邮寄地址
Name of Person Making this Declaration:声明人
Title:头衔

【iOS】苹果IPA(内购)_第9张图片
3.png

然后是又是一堆关于在美国纳税的东西,没有在美国纳税,不用管

【iOS】苹果IPA(内购)_第10张图片
4.png

红框框里面的打勾,submit

【iOS】苹果IPA(内购)_第11张图片
5.png

哦克,银行信息、协议、税务信息填写完成了。鼓掌!!还有天写完信息之后会有24小时的审核时间(一般十几个小时就差不多了),审核期间是不能修改任何信息的,所以一定要认真核对信息。

三、添加内购商品

在此添加内购商品,点加号


【iOS】苹果IPA(内购)_第12张图片
内购商品
内购商品分为四类:

1、消耗型商品:类似游戏中的金币,一些APP中的货币,比如王者荣耀的钻石。会被消耗的,要选择消耗型商品
2、非消耗型商品:无法被消耗的商品,一次购买,就应该永久可以使用的
注意:当你使用非消耗型商品时,你需要添加一个恢复购买的按钮
这个常见于各种游戏中,其实知道这个规定以后还是挺好理解的,非消耗型商品是不可被消耗的,一次购买终身使用的,非消耗型的商品是跟appleId绑定的,就是你平时下载APP让你输入账号密码的内个。
你需要一个恢复购买的按钮,来让用户恢复他购买的内容
3、订阅类型商品:如果你的公司是外包公司,有订阅类型商品的APP一定要用客户的账号提交审核,因为当APP中有过订阅类型商品,注意是有过,创建过再删除也算,这个APP无法被转移账号
注意:使用或曾经使用过订阅型商品的APP无法转移
4、非续期订阅

内购商品所需要填写的信息:

1、参考名称:再购买时会显示的名称
2、产品ID:好区分的话用AppID+数字比较好,例如:baifenbai1
3、定价:里面有设定好的产品金额,可以选择
4、显示名称:一般和参考名称写成一样即可
5、描述:对产品的描述
6、appstore推广图片是选填,可以不填
7、审核信息-屏幕快照:添加商品描述图片(iOS 至少需要 640 x 920 像素,不能携带alpha通道),这个图片只是审核时使用。
8、审核信息-审核备注:一般填写沙盒测试帐号和密码,以方便苹果审核使用

然后保存,同时注意上线的时候添加内购项目


【iOS】苹果IPA(内购)_第13张图片
6.jpg

四、添加沙盒测试帐号

点击进入用户职能


【iOS】苹果IPA(内购)_第14张图片
用户职能

添加沙盒测试账户


【iOS】苹果IPA(内购)_第15张图片
添加沙盒测试账户

注意一定要记好密码

五、代码实现

内购的代码比较简单,这里给出的适用于消耗项目。
1)首先简单说明一下内购的流程:
1、首先拿产品ID去苹果查询商品是否存在,返回可卖商品列表
2、创建票据,去给苹果巴巴送钱,交完钱苹果巴巴印个戳
3、将印了戳的票据交给自己的服务器去验证这笔交易是否是真正的交易成功
4、拿到服务器的返回数据进行下一步操作

2)代码,比较简单,网络上大部分文章里面都会有,犯懒,等有时间封装一下是最好的
首先导入StoreKit.framework库,病遵循协议

.m文件代码

#import "AppleInChargeViewController.h"
#import 
// 产品的ID
#define ProductID1 @"com.61read.hdsgsh12"

#define NSLog(format, ...) printf("class: <%p %s:(%d) > method: %s \n%s\n", self, [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __PRETTY_FUNCTION__, [[NSString stringWithFormat:(format), ##__VA_ARGS__] UTF8String] )

@interface AppleInChargeViewController ()
{
    NSString *selectProductID;
}
@end
@implementation AppleInChargeViewController
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    // 添加观察者
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    // 移除观察者
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"内购";
    
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"测试" style:UIBarButtonItemStylePlain target:self action:@selector(test)];
    
    self.view.backgroundColor =[UIColor yellowColor];
}
#pragma mark 测试内购
-(void)test{
    
    if([SKPaymentQueue canMakePayments]){
        
        // productID就是你在创建购买项目时所填写的产品ID
        selectProductID = [NSString stringWithFormat:@"%@",ProductID1];
        [self requestProductID:selectProductID];
        
    }else{
        
        // NSLog(@"不允许程序内付费");
        UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"温馨提示"
                                                             message:@"请先开启应用内付费购买功能。"
                                                            delegate:nil
                                                   cancelButtonTitle:@"确定"
                                                   otherButtonTitles: nil];
        [alertError show];
    }
}
#pragma mark 1.请求所有的商品ID
-(void)requestProductID:(NSString *)productID{
    
    // 1.拿到所有可卖商品的ID数组
    NSArray *productIDArray = [[NSArray alloc]initWithObjects:productID, nil];
    NSSet *sets = [[NSSet alloc]initWithArray:productIDArray];
    
    // 2.向苹果发送请求,请求所有可买的商品
    // 2.1.创建请求对象
    SKProductsRequest *sKProductsRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:sets];
    // 2.2.设置代理(在代理方法里面获取所有的可卖的商品)
    sKProductsRequest.delegate = self;
    // 2.3.开始请求
    [sKProductsRequest start];
    
}
#pragma mark 2.苹果那边的内购监听
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    
    NSLog(@"可卖商品的数量=%ld",response.products.count);
    
    NSArray *product = response.products;
    if([product count] == 0){
        
        NSLog(@"没有商品");
        return;
    }
    
    for (SKProduct *sKProduct in product) {
        
        NSLog(@"pro info");
        NSLog(@"SKProduct 描述信息:%@", sKProduct.description);
        NSLog(@"localizedTitle 产品标题:%@", sKProduct.localizedTitle);
        NSLog(@"localizedDescription 产品描述信息:%@",sKProduct.localizedDescription);
        NSLog(@"price 价格:%@",sKProduct.price);
        NSLog(@"productIdentifier Product id:%@",sKProduct.productIdentifier);
        
        if([sKProduct.productIdentifier isEqualToString: selectProductID]){
            
            [self buyProduct:sKProduct];
            
            break;
            
        }else{
            
            //NSLog(@"不不不相同");
        }
    }
    
}

#pragma mark 内购的代码调用
-(void)buyProduct:(SKProduct *)product{
    
    // 1.创建票据
    SKPayment *skpayment = [SKPayment paymentWithProduct:product];
    
    // 2.将票据加入到交易队列
    [[SKPaymentQueue defaultQueue] addPayment:skpayment];
    
}

#pragma mark 4.实现观察者监听付钱的代理方法,只要交易发生变化就会走下面的方法
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    
    /*
     SKPaymentTransactionStatePurchasing,    正在购买
     SKPaymentTransactionStatePurchased,     已经购买
     SKPaymentTransactionStateFailed,        购买失败
     SKPaymentTransactionStateRestored,      回复购买中
     SKPaymentTransactionStateDeferred       交易还在队列里面,但最终状态还没有决定
     */
    
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:{
                
                NSLog(@"正在购买");
            }break;
            case SKPaymentTransactionStatePurchased:{
                
                NSLog(@"购买成功");
                // 购买后告诉交易队列,把这个成功的交易移除掉
                [queue finishTransaction:transaction];
                [self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction];
            }break;
            case SKPaymentTransactionStateFailed:{
                
                NSLog(@"购买失败");
                // 购买失败也要把这个交易移除掉
                [queue finishTransaction:transaction];
            }break;
            case SKPaymentTransactionStateRestored:{
                NSLog(@"回复购买中,也叫做已经购买");
                // 回复购买中也要把这个交易移除掉
                [queue finishTransaction:transaction];
            }break;
            case SKPaymentTransactionStateDeferred:{
                
                NSLog(@"交易还在队列里面,但最终状态还没有决定");
            }break;
            default:
                break;
        }
    }
}

// 苹果内购支付成功
- (void)buyAppleStoreProductSucceedWithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp {
    
    NSString * productIdentifier = paymentTransactionp.payment.productIdentifier;
    // NSLog(@"productIdentifier Product id:%@", productIdentifier);
    NSString *transactionReceiptString= nil;
    
    //系统IOS7.0以上获取支付验证凭证的方式应该改变,切验证返回的数据结构也不一样了。
    NSString *version = [UIDevice currentDevice].systemVersion;
    if([version intValue] >= 7.0){
        // 验证凭据,获取到苹果返回的交易凭据
        // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
        NSURLRequest * appstoreRequest = [NSURLRequest requestWithURL:[[NSBundle mainBundle]appStoreReceiptURL]];
        NSError *error = nil;
        NSData * receiptData = [NSURLConnection sendSynchronousRequest:appstoreRequest returningResponse:nil error:&error];
        transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    }else{
        
        NSData * receiptData = paymentTransactionp.transactionReceipt;
        //  transactionReceiptString = [receiptData base64EncodedString];
        transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    }
    // 去验证是否真正的支付成功了
    [self checkAppStorePayResultWithBase64String:transactionReceiptString];
    
}

- (void)checkAppStorePayResultWithBase64String:(NSString *)base64String {
    
    /* 生成订单参数,注意沙盒测试账号与线上正式苹果账号的验证途径不一样,要给后台标明 */
    /*
     注意:
     自己测试的时候使用的是沙盒购买(测试环境)
     App Store审核的时候也使用的是沙盒购买(测试环境)
     上线以后就不是用的沙盒购买了(正式环境)
     所以此时应该先验证正式环境,在验证测试环境
     
     正式环境验证成功,说明是线上用户在使用
     正式环境验证不成功返回21007,说明是自己测试或者审核人员在测试
     */
    /*
     苹果AppStore线上的购买凭证地址是: https://buy.itunes.apple.com/verifyReceipt
     测试地址是:https://sandbox.itunes.apple.com/verifyReceipt
     */
    //    NSNumber *sandbox;
    NSString *sandbox;
#if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG))
    //sandbox = @(0);
    sandbox = @"0";
#else
    //sandbox = @(1);
    sandbox = @"1";
#endif
    
    NSMutableDictionary *prgam = [[NSMutableDictionary alloc] init];;
    [prgam setValue:sandbox forKey:@"sandbox"];
    [prgam setValue:base64String forKey:@"reciept"];
    
    /*
     请求后台接口,服务器处验证是否支付成功,依据返回结果做相应逻辑处理
     0 代表沙盒  1代表 正式的内购
     最后最验证后的
     */
    /*
     内购验证凭据返回结果状态码说明
     21000 App Store无法读取你提供的JSON数据
     21002 收据数据不符合格式
     21003 收据无法被验证
     21004 你提供的共享密钥和账户的共享密钥不一致
     21005 收据服务器当前不可用
     21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
     21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
     21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
     */
    
    NSLog(@"字典==%@",prgam);
    
}

#pragma mark 客户端验证购买凭据
- (void)verifyTransactionResult
{
    // 验证凭据,获取到苹果返回的交易凭据
    // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    // 从沙盒中获取到购买凭据
    NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
    // 传输的是BASE64编码的字符串
    /**
     BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
     BASE64是可以编码和解码的
     */
    NSDictionary *requestContents = @{
                                      @"receipt-data": [receipt base64EncodedStringWithOptions:0]
                                      };
    NSError *error;
    // 转换为 JSON 格式
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                          options:0
                                                            error:&error];
    // 不存在
    if (!requestData) { /* ... Handle error ... */ }
    
    // 发送网络POST请求,对购买凭据进行验证
    NSString *verifyUrlString;
#if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG))
    verifyUrlString = @"https://sandbox.itunes.apple.com/verifyReceipt";
#else
    verifyUrlString = @"https://buy.itunes.apple.com/verifyReceipt";
#endif
    // 国内访问苹果服务器比较慢,timeoutInterval 需要长一点
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:[[NSURL alloc] initWithString:verifyUrlString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
    
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:requestData];
    
    // 在后台对列中提交验证请求,并获得官方的验证JSON结果
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                               if (connectionError) {
                                   NSLog(@"链接失败");
                               } else {
                                   NSError *error;
                                   NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
                                   if (!jsonResponse) {
                                       NSLog(@"验证失败");
                                   }
                                   
                                   // 比对 jsonResponse 中以下信息基本上可以保证数据安全
                                   /*
                                    bundle_id
                                    application_version
                                    product_id
                                    transaction_id
                                    */
                                   
                                   NSLog(@"验证成功");
                               }
                           }];
    
}

六、内购测试

连接真机,真机退出自己的已登录的AppID,先不去登录沙盒测试帐号,因为沙盒账号是一个假的AppleID账号,所以在iphone上沙盒测试帐号是登录不成功的。强行登录的话


【iOS】苹果IPA(内购)_第16张图片
这是强行登录沙盒测试帐号的提示

唤起内购支付,会提示展示出商品的相关信息,并提示登录AppID,输入沙盒测试账户和密码,这里注意第一次登录可能会失败,没关系再次登录就好。
如果之前的几个步骤没有出错的话,到这里就应该是拿到了支付成功的提示了,内购流程也就走完了。amazing!

七、内购注意事项

内购看起来比较复杂,一步步来还是比见简单的,但是要仔细,有时候坑也是不少的。下面记录一下需要注意的事项:

1、测试的时候需要使用没有越狱的真机!!!

2、沙盒测试账户不能再Appstore直接登录,需要在唤起支付的时候在弹出框里面进行登录

3、在测试期间和苹果审核期间使用的都是沙盒测试帐号,在上架之后使用的才是真实帐号,要做好区分

4、项目的Bundle identifier需要与申请AppID时填写的bundleID一致,不然会无法请求到商品信息。

5、银行信息、税务信息等不完整或过期,不完整的信息不能进行内购测试

6、内购产品是和应用状态挂钩的,应用出于待发布状态是无法进行内购测试的;应用被拒绝之后内购商品同样会被拒绝,再次提交审核的时候需要将内购商品设为Cleared for Sale

你可能感兴趣的:(【iOS】苹果IPA(内购))