标签:des style blog http color io os 使用 ar
转自:http://blog.sina.com.cn/s/blog_4b55f6860100sbfb.html
第一印象觉得In-App Purchase(简称IAP)非常简单。Apple提供的大量文档应该让开发者很快熟悉地熟悉。那么,为什么在你的应用中集成IAP特性就如此令人生厌呢?
这是因为在开发过程中不可避免会出现一些错误。而但这些错误发生的时候,你就抓瞎了。虽然Apple提供了有关IAP的大量文档,但他们并未提及集成 IAP的详细步骤。而且对StoreKit集成过程中出现的问题也没有一个核对清单。另外对于为什么诸如产品ID非法之类的问题也没有提供NSError 之类的对象来告诉你原因。
在试用了各种可能的解决方案后,你只能身心疲惫,彷徨无助。
为了提高你的效率和减少你的痛苦,我觉定利用此文来介绍一下实现IAP的详细步骤。本文很详细,有点长。甚至可能太长了,但不像Apple的文档,它提供了为实现IAP的每一个步骤。
废话少说,我们直入主题吧。
IAP能正常工作的秘诀:分成两个步骤:
第一个步骤是你可能遇到问题的部分。一旦你在代码中成功地获取了产品描述,编写购买产品的代码不过是小菜一碟。
我们先看看步骤1。
下面是有关创建产品及提取其描述的非常粗略的步骤:
提取产品描述的代码非常简单,但其他步骤则很容易错。
注意: 为提取产品描述,你并不需要在iTunes Connect中创建IAP测试用户。
为支持IAP,你的App ID不能包括通配符(“*”)。为确定你的App Id是否包括通配符,请登录http://developer.apple.com/iphone,在 iPhone Developer Program Portal中选择左边菜单中的 “App IDs”检查你的 App ID。
下面是一个唯一的App ID:
7DW89RZKLY.com.runmonster.runmonsterfree
下面不是一个唯一的 App ID:
7DW89RZKLY.com.runmonster.*
如果你还没有一个唯一的App ID,按如下步骤创建一个:
在创建了新的App ID后,你需要生成一个指向这个App ID的新provisioning profile。
下面就是令人痛苦的生成和安装新provisioning profile的详细步骤:
在Xcode中安装了 profile 文件后,你需要对使用此provisiong profile的项目进行一些编辑工作:
如果你的程序已经发表到App Store了,那么可以略过此步骤。
在你将产品添加到 iTunes Connect之前,你必须添加此产品所需的程序。如果你的程序还没有100%完成也无需担心,你可以先提交具有部分数据的程序,最后再提交真实的程序。
注意: 只有 SKU 和 version(版本)部分是以后不可修改的
Apple的文档中没有任何地方提及详情,但它却是必须的步骤。要成功测IAP功能,你必须提交程序的二进制码。即使你的程序还没有100%完成,你仍然需要提交二进制码。然而,你也可以立即摈弃你的二进制码,使其不会进入审核阶段。
下面这些步骤非常关键,我可是因为少做了某些步骤而度过了一段非常痛苦的时间:
不用担心,由于程序的状态是“Developer Rejected”,Apple是不会对其进行审核的。你可以在任何时候提交程序的新版本并使其状态为“Developer Rejected”,这不会对以后程序正式提交的等待时间有任何影响。
完成了以上所有步骤后,我们最终可以向iTunes Connect中添加产品了。
下面我们开始编写代码对刚加入到iTunes Connect中的产品信息进行提取。我访问产品数据,我们需要使用 StoreKit framework。
注意: StoreKit 无法在模拟器上工作。你必须在真机上进行测试。
1
2 3 4 5 6 7 8 9 10 11 |
// InAppPurchaseManager.h
#import <StoreKit/StoreKit.h> #define kInAppPurchaseManagerProductsFetchedNotification @"kInAppPurchaseManagerProductsFetchedNotification" @interface InAppPurchaseManager : NSObject <SKProductsRequestDelegate> { SKProduct *proUpgradeProduct; SKProductsRequest *productsRequest; } |
注意: InAppPurchaseManager 是一个单例类,它处理程序中所有IAP任务。它是本文中的示例程序。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// InAppPurchaseManager.m
- (void)requestProUpgradeProductData { NSSet *productIdentifiers = [NSSet setWithObject:@"com.runmonster.runmonsterfree.upgradetopro" ]; productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers]; productsRequest.delegate = self; [productsRequest start]; // we will release the request object in the delegate callback } #pragma mark - #pragma mark SKProductsRequestDelegate methods - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { NSArray *products = response.products; proUpgradeProduct = [products count] == 1 ? [[products firstObject] retain] : nil; if (proUpgradeProduct) { NSLog(@"Product title: %@" , proUpgradeProduct.localizedTitle); NSLog(@"Product description: %@" , proUpgradeProduct.localizedDescription); NSLog(@"Product price: %@" , proUpgradeProduct.price); NSLog(@"Product id: %@" , proUpgradeProduct.productIdentifier); } for (NSString *invalidProductId in response.invalidProductIdentifiers) { NSLog(@"Invalid product id: %@" , invalidProductId); } // finally release the reqest we alloc/init’ed in requestProUpgradeProductData [productsRequest release]; [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerProductsFetchedNotification object:self userInfo:nil]; } |
上面代码有几点需要注意:
1
2 3 4 5 6 7 8 9 10 |
// SKProduct+LocalizedPrice.h
#import <Foundation/Foundation.h> #import <StoreKit/StoreKit.h> @interface SKProduct (LocalizedPrice) @property (nonatomic, readonly) NSString *localizedPrice; @end |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// SKProduct+LocalizedPrice.m
#import "SKProduct+LocalizedPrice.h" @implementation SKProduct (LocalizedPrice) - (NSString *)localizedPrice { NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; [numberFormatter setLocale:self.priceLocale]; NSString *formattedString = [numberFormatter stringFromNumber:self.price]; [numberFormatter release]; return formattedString; } @end |
加入上述代码,测试一下。你应该在控制台窗口中看见产品信息了。然而更大的可能是,你得到了一个无效的产品id。我下一篇文章将介绍怎样对这个问题进行调试。但是,下面的步骤8有可能是阻碍你前进的障碍。
遵循了上述所有步骤,但是你的产品仍然是无效的?你是否两次,三次,四次不懈努力地确认你是否遵循了上面提到的每个步骤?你是否已经对网上IAP信息少得可怜而感到绝望?
那么,你应该等待。
你的产品要进入iTunes Connect使得Apple准备好沙箱环境需要一些时间。对于我而言,我是经过了无数次产品无效错误的绝望。而在24小时后,我没有修改任何一行代码, 但产品id变为有效。我认为要使产品发布到Apple的网络系统需要几个小时的时间,但如果你有时间的话,你可以像我一样等上24个小时。
至此你应该已经成功地获取了 SKProduct 描述。比较而言,支持购买产品相对简单些。仅需下面三个步骤:
我们从编写支持事务所需代码开始。
首先注意:你将负责开发产品购买的用户界面。StoreKit 未提供任何与用户界面相关的元素。如果你希望你的购买用户界面与App Store一样,那么你要自己完成。
下面所有代码都是有关事务处理的后台部分。这是一个单独的类只有一条简单的API以供外部类(比如view controller)调用进行购买。如果你找到将其集成到你程序的购买部分的方法,那么我推荐你使用类似方案。
首先,需要遵循 SKPaymentTransactionObserver 协议:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// InAppPurchaseManager.h
// add a couple notifications sent out when the transaction completes #define kInAppPurchaseManagerTransactionFailedNotification @"kInAppPurchaseManagerTransactionFailedNotification" #define kInAppPurchaseManagerTransactionSucceededNotification @"kInAppPurchaseManagerTransactionSucceededNotification" … @interface InAppPurchaseManager : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver> { … } // public methods - (void)loadStore; - (BOOL)canMakePurchases; - (void)purchaseProUpgrade; @end |
上面我们定义了两个新的notification,它们将作为购买事务的结果被发送。在上例中我们仍然使用与获取产品描述同一个InAppPurchaseManager类。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
// InAppPurchaseManager.m
#define kInAppPurchaseProUpgradeProductId @"com.runmonster.runmonsterfree.upgradetopro" … #pragma - #pragma Public methods // // call this method once on startup // - (void)loadStore { // restarts any purchases if they were interrupted last time the app was open [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; // get the product description (defined in early sections) [self requestProUpgradeProductData]; } // // call this before making a purchase // - (BOOL)canMakePurchases { return [SKPaymentQueue canMakePayments]; } // // kick off the upgrade transaction // - (void)purchaseProUpgrade { SKPayment *payment = [SKPayment paymentWithProductIdentifier:kInAppPurchaseProUpgradeProductId]; [[SKPaymentQueue defaultQueue] addPayment:payment]; } #pragma - #pragma Purchase helpers // // saves a record of the transaction by storing the receipt to disk // - (void)recordTransaction:(SKPaymentTransaction *)transaction { if ([transaction.payment.productIdentifier isEqualToString:kInAppPurchaseProUpgradeProductId]) { // save the transaction receipt to disk [[NSUserDefaults standardUserDefaults] setValue:transaction.transactionReceipt forKey:@"proUpgradeTransactionReceipt" ]; [[NSUserDefaults standardUserDefaults] synchronize]; } } // // enable pro features // - (void)provideContent:(NSString *)productId { if ([productId isEqualToString:kInAppPurchaseProUpgradeProductId]) { // enable the pro features [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isProUpgradePurchased" ]; [[NSUserDefaults standardUserDefaults] synchronize]; } } // // removes the transaction from the queue and posts a notification with the transaction result // - (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful { // remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:transaction, @"transaction" , nil]; if (wasSuccessful) { // send out a notification that we’ve finished the transaction [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionSucceededNotification object:self userInfo:userInfo]; } else { // send out a notification for the failed transaction [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionFailedNotification object:self userInfo:userInfo]; } } // // called when the transaction was successful // - (void)completeTransaction:(SKPaymentTransaction *)transaction { [self recordTransaction:transaction]; [self provideContent:transaction.payment.productIdentifier]; [self finishTransaction:transaction wasSuccessful:YES]; } // // called when a transaction has been restored and and successfully completed // - (void)restoreTransaction:(SKPaymentTransaction *)transaction { [self recordTransaction:transaction.originalTransaction]; [self provideContent:transaction.originalTransaction.payment.productIdentifier]; [self finishTransaction:transaction wasSuccessful:YES]; } // // called when a transaction has failed // - (void)failedTransaction:(SKPaymentTransaction *)transaction { if (transaction.error.code != SKErrorPaymentCancelled) { // error! [self finishTransaction:transaction wasSuccessful:NO]; } else { // this is fine, the user just cancelled, so don’t notify [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } } #pragma mark - #pragma mark SKPaymentTransactionObserver methods // // called when the transaction status is updated // - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)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; default: break; } } } |
要测试上面的新代码,你还需要编写调用 loadStore, canMakePurchases 以及 purchaseProUpgrade 方法的代码。
有关上述代码的详细解释,请参考官方 In App Purchase Programming Guide (IAP编程指南)
上述代码有几个部分是针对我的程序的。例如,在 provideContent:中,NSUserDefaults 中的@”isProUpgradePurchased” BOOL 字段被设定为 YES。程序的其他部分将检查此BOOL值以确定是否需要启动专业版功能。如果你正好也要实现免费升级专业版的功能,那么你可以使用同样的方法。
为测试上述代码,你需要在 iTunes Connect 中创建测试用户以对IAP功能进行测试。你可以使用测试帐号购买产品而不被Apple收取费用。
按以下步骤创建测试用户:
测试时你需要输入这些email地址和密码。
在进行程序购买功能测试前,你必须在你的设备中退出iTunes Store。遵循以下步骤:
现在,终于可以开始进行IAP功能的测试了。测试很简单:
如果你使用同一账户进行购买时,系统将提示你已经购买了此产品。按“Yes”就可以再次下载此产品。
实现IAP功能比想象的要复杂许多。我可是经过无数痛苦的经历才完成我的程序。希望能够帮助其他开发者减轻他们的痛苦。
标签:des style blog http color io os 使用 ar
原文地址:http://www.cnblogs.com/Androider123/p/4021608.html