为什么鬼佬写的东西特别好,难道他们都是富二代,官三代,吃饱没事撑着分享知识,其实我们也可以,只是需要坚强的内心,淡定淡定,(也和思维习惯有关,我们中国人,内敛崇尚儒家思想,以前,现在我们可是马力十足的愤怒民族)
别一个劲往下看,此鬼佬的主题是他的这个方法是一个很笨的方法,在我们看来,他的做法和他这个人简直”猪狗不如“,但我们连一个原创的方法也交不出就还是乖乖地“师夷长技”吧,他心底里建议你去看《iOs 6 by tutorial第三版》
If you're new here, you may want to subscribe to my RSS feed or follow me on Twitter. Thanks for visiting!
This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer.
A while back on the weekly tutorial poll on the sidebar, you guys voted for a tutorial on In-App Purchases: consumables and receipt validation. As always, your wish is my command! :]
This tutorial picks up where we left things off in our previous In-App Purchases tutorial. As a reminder, in that tutorial we developed an app called “In App Rage” where customers could purchase rage face comics as non-consumable In-App purchases.
In this tutorial, you will extend the app to add a new consumable In-App Purchase – after all, those are often way more lucrative than non-consumables, since customers can purchase them more than once! You will also add receipt validation to add some extra security into the app.
Note that this tutorial (and the previous) are focused on the “simplest and easiest” way to do things, not the most “robust and flexible” way of doing things. If you’re more interested in the latter, check out our book iOS 6 by Tutorials, where you’ll learn how to develop an extremely flexible server-based In-App Purchases system, with downloadable content hosted on Apple’s servers!
So without further ado, let’s dive into some more In-App Purchases info – you’ll see it’s pretty easy, and there’s no need to rage about it! :]
Getting Started
Start by downloading the starter project for this tutorial.
Note that the starter project is not the same as where we left things off in the last tutorial – I’ve added a few extra things for you:
- Added the ability to see a rage comic after purchasing it (which was left as an exercise at the end of last tutorial).
- Added the skeleton for the “random rage face” feature. Right now you can do this an unlimited amount of times for testing.
- Gratuitous icons and iPhone 5 screen size support! :]
You’ll have to make a few changes to this project for it to work for you:
- Open RageIAPHelper.m and MasterViewController.m and search for “com.razeware” – you’ll see the product identifiers I set up. Replace those with your own product identifiers you created last time in iTunes Connect.
- Open In App Rage-Info.plist and update your Bundle Identifier to your own bundle identifier.
Once you’re done, try out the app on your device and purchase a comic and make sure the purchase succeeds, and that you can view the comic after you buy it. Also, try out the new random rage face feature!
Creating a Consumable In-App Purchase
You have an sneaky plan to make money – instead of allowing the user to see cool random rage faces as often as they’d like, instead you’ll add an arbitrary limit to it, and charge users to purchase more, mwuhahaha!
This way, there’s no limit to how much money users can spend on your app, hopefully keeping the bucks rolling in!
Note: This is just the beginning of ways you can use consumable In-App Purchases to (arguably sneakily) increase your app’s revenue. To learn more, check out the many blogs and talks about freemium game design, gamer psychology, or any games by Zynga ;]
Just like you did with consumable In-App Purchases, the first step to make this all happen is to create a new In-App Purchase entry in iTunes Connect.
So log onto iTunes Connect, and click Manage Your Applications. Click your entry for In-App Rage, clickManage In-App Purchases, and Create New. Underneath the Consumable option, click Select.
Next, fill out the screen that appears as follows (but replace razeware with your own name/company name):
Finally, scroll down to the Language section and click Add Language. Fill in the dialog that appears as follows:
Click Save, then Save again, and you’re done! Time to try this out in your app.
Implementing a Consumable In-App Purchase
To begin, open RageIAPHelper.m and add the new product identifier you jsut created to the bottom of the list of productIdentifiers, similar to the following:
NSSet * productIdentifiers = [NSSet setWithObjects:
@"com.razeware.inapprage.drummerrage",
@"com.razeware.inapprage.itunesconnectrage",
@"com.razeware.inapprage.nightlyrage",
@"com.razeware.inapprage.studylikeaboss",
@"com.razeware.inapprage.updogsadness",
@"com.razeware.inapprage.randomrageface",
nil];
Next, open IAPHelper.m and modify your provideContentForProductIdentifier: method as follows:
- (void)provideContentForProductIdentifier:(NSString *)productIdentifier {
if ([productIdentifier isEqualToString:@"com.razeware.inapprage.randomrageface"]) {
int currentValue = [[NSUserDefaults standardUserDefaults]
integerForKey:@"com.razeware.inapprage.randomrageface"];
currentValue += 5;
[[NSUserDefaults standardUserDefaults] setInteger:currentValue
forKey:@"com.razeware.inapprage.randomrageface"];
[[NSUserDefaults standardUserDefaults] synchronize];
} else {
[_purchasedProductIdentifiers addObject:productIdentifier];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
}
[[NSNotificationCenter defaultCenter]
postNotificationName:IAPHelperProductPurchasedNotification
object:productIdentifier userInfo:nil];
}
Here you add some special case behavior for the consumable In-App Purchase you added. When a customer purchases this, instead of simply setting a flag indicating whether it’s purchased or not, you increment a value that keeps track of how many rolls the user currently has.
Note: This works but isn’t the ideal way of doing things, because now your IAPHelper class has hard-coded logic for a particular In-App Purchase so isn’t as reusable. For a more reusable way of doing things, check outiOS 6 by Tutorials.
Next open MasterViewController.m and replace your tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: methods as follows:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _products.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"
forIndexPath:indexPath];
SKProduct * product = (SKProduct *) _products[indexPath.row];
cell.textLabel.text = product.localizedTitle;
[_priceFormatter setLocale:product.priceLocale];
cell.detailTextLabel.text = [_priceFormatter stringFromNumber:product.price];
if (![product.productIdentifier isEqualToString:@"com.razeware.inapprage.randomrageface"] &&
[[RageIAPHelper sharedInstance] productPurchased:product.productIdentifier]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
cell.accessoryView = nil;
} else {
UIButton *buyButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
buyButton.frame = CGRectMake(0, 0, 72, 37);
[buyButton setTitle:@"Buy" forState:UIControlStateNormal];
buyButton.tag = indexPath.row;
[buyButton addTarget:self action:@selector(buyButtonTapped:)
forControlEvents:UIControlEventTouchUpInside];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.accessoryView = buyButton;
}
return cell;
}
Here you have removed the “test” row that the sample project had earlier, since now you have a new consumable In-App Purchase to display instead. You also add some logic so that you always display the “Buy” button for the consumable IAP.
Finally, open RandomFaceViewController.m and replace the buttonTapped: method with this (and these additional two methods):
- (void)refresh {
int currentValue = [[NSUserDefaults standardUserDefaults]
integerForKey:@"com.razeware.inapprage.randomrageface"];
self.label.text = [NSString stringWithFormat:@"Times Remaining: %d", currentValue];
}
- (void)viewWillAppear:(BOOL)animated {
[self refresh];
}
- (IBAction)buttonTapped:(id)sender {
int currentValue = [[NSUserDefaults standardUserDefaults]
integerForKey:@"com.razeware.inapprage.randomrageface"];
if (currentValue <= 0) return;
currentValue--;
[[NSUserDefaults standardUserDefaults] setInteger:currentValue
forKey:@"com.razeware.inapprage.randomrageface"];
[self refresh];
int randomIdx = (arc4random() % 4) + 1;
NSString * randomName = [NSString stringWithFormat:@"random%d.png", randomIdx];
self.imageView.image = [UIImage imageNamed:randomName];
}
This is some basic logic to replace the unlimited button taps with using the new value you’re using to track the user’s spins.
And that’s it! Build and run, and purchase a few spins. Then test out your the random rage face button and make sure it works as expected!
Receipt Validation
When you make an In-App Purchase, you can’t 100% trust that the response that comes over the network saying “everything went OK” really came from Apple without using a technique called “receipt validation.”
When you make an In-App Purchase, Apple sends you back a special piece of data called a “receipt.” This is a private piece of data that records cryptographically-signed information about the transaction. The idea is that for your app to be secure, you shouldn’t blindly trust that a purchase completed – you should send the receipt to a special “receipt validation” server that Apple has set up to double-check that everything is OK.
The dangers of not performing receipt validation were proven pretty spectacularly recently. A Russian hacker developed an easy-to-install In-App Purchase hack that allowed users to receive almost any In-App Purchase for free – at least, if the app wasn’t doing proper receipt validation.
The hack is a classic “man in the middle” attack. You configure DNS records so your devices is routed to the “hack” servers rather than Apple, and you configure a fake certificate on your device so the OS trusts the “hack server” you’re connected to. Then, whenever you make a request to make an In-App Purchase, the request goes to the “hack server” instead. The hack server will always say “done and paid for!” so the app will unlock the content without knowing it’s been had – for free!
This hack will no longer work on iOS 6. However, there are other variants hackers could employ in the future, so it’s still a good idea to use receipt validation.
Note: For more information on the In-App Purchase hack described above, check out this article.
The No-Server Solution
Apple’s official recommendation to perform receipt validation is to connect to your own server, which then connects to Apple’s servers to validate the receipts.
For a number of reasons, this is more secure than connecting to Apple directly, and this is the approach you take iniOS 6 by Tutorials.
But in this tutorial, the focus is on the simplest and easiest way to implement In-App Purchases, and it would be a major pain to have to set up your own server just to validate receipts.
A lot of other developers feel the same way, and so they wrote code to connect to Apple’s validation server directly from their apps rather than going through an intermediate server (despite Apple’s recommendations). It became so common that Apple provided some sample code demonstrating a fairly secure way to verify receipts by connecting to Apple’s servers directly.
In this section, you’re going to integrate Apple’s provided code into the app to validate receipts before unlocking the purchases.
Note: The original code was missing some pieces (such as the base64 routines) as well as some logic (like returning results to a caller), which I added in. Also, I cannot vouch 100% for the robustness of the Apple code, as it looks a little thrown together, so use at your own risk!
Go ahead and download the resources for this tutorial, and drag the folder into your project. Make sure Copy items into destination group’s folder (if needed) is checked, Create groups for any added folders is selected, and the In-App Rage target is checked, and click Finish.
The code requires the Security framework, so let’s add it to your project. To do this, click on your In App Rageproject root in the Project Navigator and then the In App Rage target. Select the Build Phases tab, scroll down to the Link Binary with Libraries section, expand it if necessary, and click the + button. Select Security.frameworkand click Add. At this point, your list of libraries should look like the following:
Then switch to IAPHelper.m and add the following import to the top of the file:
#import "VerificationController.h"
And add the following method:
- (void)validateReceiptForTransaction:(SKPaymentTransaction *)transaction {
VerificationController * verifier = [VerificationController sharedInstance];
[verifier verifyPurchase:transaction completionHandler:^(BOOL success) {
if (success) {
NSLog(@"Successfully verified receipt!");
[self provideContentForProductIdentifier:transaction.payment.productIdentifier];
} else {
NSLog(@"Failed to validate receipt.");
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
}];
}
This method simply calls Apple’s (somewhat modified) code to verify the transactions, and either provides the content or not, based on the results.
The final step is to call this new method from completeTransaction: and restoreTransaction:, instead of providing the content right away. Replace those two methods with the following:
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"completeTransaction...");
[self validateReceiptForTransaction:transaction];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"restoreTransaction...");
[self validateReceiptForTransaction:transaction];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
Build and run, and your app should work as usual – but now with a great deal more security!
Note: If you are going to use this verification controller in your app, remember that it is currently configured to use the sandbox server (see the verifyPurchase:completionHandler: method). You will need to switch this to production before you release your app.
Where To Go From Here?
Here is the final sample project from the tutorial series.
Congrats – you now have implemented both non-consumable and consumable In-App Purchases, added the ability for users to restore transactions, and are validating receipts!
Like I mentioned in the previous tutorials, for a lot of simple apps this is more than enough. But if you want to take things even further and learn how develop a robust and extensible server-based system, check out iOS 6 by Tutorials!
I hope you enjoyed this series, and wish you best of luck with your consumable In-App Purchases! Just don’t be too sneaky/evil, one Zynga is enough ;]
If you have any questions or comments on this tutorial or In-App Purchases in general, please join the forum discussion below!