新版iOS内购(IAP)完整流程

来源:互联网 发布:外国人北京奥运会知乎 编辑:程序博客网 时间:2024/06/01 07:51

新版iOS内购(IAP)完整流程

苹果内购是用来做什么的?能不能吃?

iOS内购(以下简称IAP)是你可以实现一个应用内购买各种物品的功能,最常见的就是游戏中购买的道具,比如钻石。 新版的iOS内购从申请、审核以及代码的书写都充满了恶意,下面来介绍一下IAP的基本流程和我们遇到的问题以及一些解决办法。

1.创建应用和IAP项目

首先进入苹果的iTunesConnection(https://itunesconnect.apple.com)(当然你需要一个开发者账号),登陆之后点击我的APP然后点击左上角的加号新建一个APP项目,然后填入一些信息,如下:

tip:其中最重要的是套装ID 他是AppID-BundleID的格式 这里需要提前为该App申请一个AppID以及Xcode工程对应的BundleID,只要是申请成功了就会在选择列表中显示出来,工程中的BundleID以及后面用到的AppID要与这里一致
并且,要实现内购功能,必须保证这个ID的In-App Purchase是开启状态


接下来在iTunesConnection中点击刚才创建好的APP,然后点击功能,添加一个App内购项目

这里我们需要的是消耗类项目(就是用了就没了的东东,比如金币,非消耗类的比如汽车的赛道,消耗类的项目应用较多)

* tip:这个产品ID在后面会用到,这是你这个内购项目的唯一标识符,其他的信息点击?会有说明,需要注意的是后面的审核备注中要求写测试账户的用户名和密码,下面会着重说这个沙盒测试账户*

2.沙盒测试账户

点击iTunesConnection中的用户和职能,点进去之后点击沙箱技术测试员(我习惯叫沙盒)


我这边已经创建好了我的测试账户,如果没有点加号创建一个,然后要去邮箱验证一下,按照提示验证通过后就可以了
那么这个测试账号用来做什么的呢?等下测试的时候要登录这个账户来“购买”,这样你就不会真的花钱了,是不是很爽,咳咳。

3.提交审核

个人认为这才是最坑的地方,反正要用好久才审核过,比较麻烦的是这个


协议,税务和银行业务的这个东东,这里要申请一个iOS Paid Application(付费应用合同),需要填一些信息以及银行信息之类的,具体也不是我填的,我这里找到一个博客,相对比较详细 http://blog.csdn.net/wang_we/article/details/44303295
填完这个东东之后,才可以构建新的版本,提交一个含有内购版本的APP(貌似还需要提交二进制文件之类的,反正我不是很懂),等待不知道多久后,如果没问题我们的内购项目会显示一个“需要开发人员操作”的状态,这就很high了,证明我们这个productID可以用来测试啦,啦啦啦。

4.开始写代码

接下来终于能写代码了,有点high,建一个工程,BundleID和之前申请的一致,接下来介绍一下核心的代码:

1.首先引入苹果自带的StoreKit框架,遵循两个协议SKPaymentTransactionObserver, SKProductsRequestDelegate 然后点击购买按钮事件方法中,添加如下方法 其中_currentProId 是我们之前申请的内购项目的产品ID,然后向苹果服务器发送request

- (IBAction)buyAction:(id)sender {    //单例支付队列 添加观察者    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];    //创建的内购的productID    if ([SKPaymentQueue canMakePayments]) { // 判断用户是否允许内购        [self requestProductData:_currentProId];    } else {        NSLog(@"-------用户禁止应用内付费购买------"); // 可以跳到设置界面提醒用户开启内购权限    }//向苹果服务器请求商品信息- (void)requestProductData:(NSString *)product {    NSLog(@"-------------前往请求产品信息----------");    NSArray *products = [[NSArray alloc] initWithObjects:product, nil];    NSSet *nsset = [NSSet setWithArray:products];    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];    request.delegate = self;    [request start];   }}

2.然后会受到请求的回调(三个代理方法),如果你发现response.invalidProductIdentifiers这个数组不为空,那么恭喜你,遇到个坑 后面会列出这个ID无效的check list ,如果你打印出了有效的ID,就可以往下进行了

tip:如果有多个内购项目,可以在第一步的初始化request的时候把所有的proid放在集合中,然后在这步把获取的有效的proid存到数组中,然后可以从数组中获取到所有的内购项目列表。

//收到产品请求回调- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {    NSLog(@"------------收到产品反馈--------------");    NSArray *products = response.products;    if (products.count == 0) {        NSLog(@"----------无法获得商品信息,购买失败---------");        if (response.invalidProductIdentifiers[0] != nil) {            NSLog(@"无效的 id ==========  %@",response.invalidProductIdentifiers);        }        return;    }    NSLog(@"productID:%@", response.products);    NSLog(@"支付产品数量:%lu", (unsigned long)response.products.count);    SKProduct *p = nil;    for (SKProduct *pro in products) {        NSLog(@"%@", [pro description]);        NSLog(@"%@", [pro localizedTitle]);        NSLog(@"%@", [pro localizedDescription]);        NSLog(@"%@", [pro price]);        NSLog(@"%@", [pro productIdentifier]);        if([pro.productIdentifier isEqualToString:_currentProId]){            p = pro;        }    }    SKPayment *payment = [SKPayment paymentWithProduct:p];    [[SKPaymentQueue defaultQueue] addPayment:payment];    NSLog(@"发送购买请求===");}//收到错误码的回调- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {    NSLog(@"error : %@", error); //错误码见注释        /*    Code  Description     21000  The App Store could not read the JSON object you provided.     21002  The data in the receipt-data property was malformed or missing.     21003  The receipt could not be authenticated.     21004  The shared secret you provided does not match the shared secret on file for your account.Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.     21005  The receipt server is not currently available.     21006  This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.     21007  This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.     21008  This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead. */        }- (void)requestDidFinish:(SKRequest *)request {    NSLog(@"---------反馈信息结束-----"); //progress dismiss 如果有菊花效果在这里停止}

**tip:proid无效的check list:https://onevcat.com/2013/11/ios-iap-checklist/
着重看审核结果以及各种ID是否匹配,还有测试手机不能越狱,如果都满足了,再去看别的地方。**

3.监听购买结果,这里有5个state,我们针对这些状态来作不同的处理,当检测到交易完成后去苹果服务器验证凭证

tip:关于验证凭证:是为了避免越狱软件模拟苹果请求达到非法购买问题,如果不考虑这个原因,可以不实现这一步
tip:测试的时候记得在设置里的iTunes Store与App Store选项中原来的账号注销掉。

// 监听购买结果- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {    for (SKPaymentTransaction *transaction in transactions) {        switch (transaction.transactionState) {            case SKPaymentTransactionStatePurchasing:                NSLog(@"---------交易中,等待用户登录-------");                //可以显示一个progress                break;            case SKPaymentTransactionStatePurchased:                NSLog(@"---------交易完成------- 接下来发送到苹果服务器验证凭证");                [self verify:(transaction)];                break;            case SKPaymentTransactionStateFailed:                                [self failedTransaction:transaction];                NSLog(@"请求失败");                break;            case SKPaymentTransactionStateRestored:                NSLog(@"---------重复购买-------");               // [self restoreTransaction:transaction];                break;            case SKPaymentTransactionStateDeferred:                NSLog(@"---------被其他操作中断-----");                //目前找到的文档 不够健全 和purchasing有点像 不容易捕捉这个状态                break;            default:                break;        }    }}

4.去苹果服务器验证并拿到凭证,然后可以放心给用户发放产品啦。

tip:用下面的方法是最新的验证的方法,支持iOS10,之前的方法被弃用了.
tip:二次验证,请注意区分宏, 测试用沙盒验证,App Store审核的时候也使用的是沙盒购买,所以验证购买凭证的时候需要判断返回Status Code决定是否去沙盒进行二次验证,为了线上用户的使用,验证的顺序肯定是先验证正式环境,此时若返回值为21007,就需要去沙盒二次验证,因为此购买的是在沙盒进行的。

//沙盒测试环境验证#define SANDBOX @"https://sandbox.itunes.apple.com/verifyReceipt"//正式环境验证#define AppStore @"https://buy.itunes.apple.com/verifyReceipt"- (void)verify:(SKPaymentTransaction *)transaction {        NSLog(@"verify");    NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL];    NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl];    NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串    NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接请求数据    NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];    //创建请求到苹果官方进行购买验证    NSURL *url=[NSURL URLWithString:SANDBOX]; // 正式上架用 AppStore    NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url];    requestM.HTTPBody=bodyData;    requestM.HTTPMethod=@"POST";    //创建连接并发送同步请求    NSLog(@"创建连接并发送同步请求");    NSError *error=nil;    NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error];    if (error) {        NSLog(@"---验证购买过程中发生错误,错误信息---:%@",error.localizedDescription);        return;    }    NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];    NSLog(@"-------验证成功后返回的的json--------%@",dic);    if([dic[@"status"] intValue] == 0){        NSLog(@"购买成功-------");        NSDictionary *dicReceipt= dic[@"receipt"];        NSDictionary *dicInApp=[dicReceipt[@"in_app"] firstObject];        NSString *productIdentifier= dicInApp[@"product_id"];//读取产品标识                [self saveInformation:productIdentifier];        [self completeTransaction:transaction]; //代理方法 不知道会不会走 如果不走也可以把verify放这里    }else{        //验证失败,可能是非法手段,数量没有 +1        NSLog(@"购买失败,未通过验证!");        [self completeTransaction:transaction];    }}// 验证成功后 说明该用户正常交易,保存信息,并且可以给用户金币啦。这边我用的NSUserDefaults保存,这样会导致用户换手机或者卸载重新下载之后信息丢失,如果不希望这样,可以和用户的账户绑定后保存到公司的服务器里面。- (void)saveInformation:(NSString *)proId {    NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];    if ([proId isEqualToString:self.currentProId]) {        NSInteger purchasedCount=[defaults integerForKey:proId];//已购买数量        [[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:proId];    }else{        [defaults setBool:YES forKey:proId];    }    //在此处对购买记录进行存储,可以存储到公司的服务器端 这里用的 UserDefaults    //       NSLog(@"====数量===%@",  [[NSUserDefaults standardUserDefaults] objectForKey:productIdentifier]);    //       这里可以让用户的金币+1 并保存用户金币数量    //验证成功 结束交易}

5.交易结束后的处理

//正常交易结束- (void)completeTransaction:(SKPaymentTransaction *)transaction{    NSLog(@"-------交易结束-------");    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];}//交易失败- (void)failedTransaction:(SKPaymentTransaction *)transaction {    if(transaction.error.code == SKErrorPaymentCancelled) {        NSLog(@"用户取消交易");    } else {        NSLog(@"交易失败");    }    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];    // 都要有结束交易的方法}- (void)restoreTransaction:(SKPaymentTransaction *)transaction {    // 对于已购商品,这里处理恢复购买的逻辑    // ********    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];}

6.附上手机界面的显示以及购买成功后控制台的打印信息


登录的是测试账户

这里会显示iTunesConnection上设置的信息,下面Sanbox代表沙盒测试账户

成功完成购买

最后收到验证后的json说明成功啦,这个json可以保存到服务器,以后可以用作漏单处理的凭证,每个购买过的产品凭证是唯一的。

完结 撒花

阅读全文
0 0