SKProductRequest doesn't fail without connection - iphone

I'd would like to handle the case in which in-app purchase products are requested without an internet connection.
When testing this case both in the simulator and a device (by turning off the wi-fi), instead of receiving a call to request:didFailWithError:, I receive a call productsRequest:didReceiveResponse: with an empty products array and then to requestDidFinish:.
Is this the expected behavior? If so, how can I know if the request failed due to a connection issue? If not, what might be wrong?
In case it helps, this is how I request the products:
- (void) requestProducts:(NSSet*)identifiers
{
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
I'm using iOS 6.

I don't know if its expected behavior because the docs are a little sparse on the subject. But I always do the checks myself so I can provide nice error messages to the user because it seems half the time the StoreKit Errors are very nondescript. Here is a bit of code I used in a recent project.
I have my own storeManager delegate to simplify calls and inheritance but it should be pretty clear whats happening.
#pragma mark - Purchase Methods
- (void)purchaseProduct:(SKProduct *)product
{
// Check Internet
if ([self checkInternetConnectionAndAlertUser:YES]) {
// Check Restrictions
if ([self checkRestrictionsAndAlertUser:YES]) {
// Check Products
if ([_products containsObject:product]) {
// Purchase the product
[[SKPaymentQueue defaultQueue] addPayment:[SKPayment paymentWithProduct:product]];
} else {
[[[UIAlertView alloc] initWithTitle:#"Error" message:#"Sorry, we couldn't find that product." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
[self.delegate purchaseDidFailWithError:[NSError errorWithDomain:#"SDInAppPurchaseManager" code:404 userInfo:#{ NSLocalizedDescriptionKey : #"Product not found." }]];
}
} else {
// Not allowed to make purchase
[self.delegate requestdidFailWithError:[NSError errorWithDomain:#"SDInAppPurchaseManager" code:500 userInfo:#{ NSLocalizedDescriptionKey : #"Not authorized to make purchases." }]];
}
} else {
// No Internet
[self.delegate requestdidFailWithError:[NSError errorWithDomain:#"SDInAppPurchaseManager" code:300 userInfo:#{ NSLocalizedDescriptionKey : #"No internet connection." }]];
}
}
#pragma mark - Checks
- (BOOL)checkInternetConnectionAndAlertUser:(BOOL)alert
{
if ([[SDDataManager dataManager] internetConnection]) {
return YES;
} else {
// Alert the user if necessary.
if (alert) {
[[[UIAlertView alloc] initWithTitle:#"No Connection" message:#"You don't appear to be connected to the internet. Please check your connection and try again." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
}
return NO;
}
}
- (BOOL)checkRestrictionsAndAlertUser:(BOOL)alert
{
if ([SKPaymentQueue canMakePayments]) {
return YES;
} else {
// Alert the user if necessary.
if (alert) {
[[[UIAlertView alloc] initWithTitle:#"Purchases Disabled" message:#"In App Purchasing is disabled for your device or account. Please check your settings and try again." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
}
return NO;
}
}

Related

iOS In app purchase doesn't call restoreTransaction on already purchased item

I am working on in app purchase with ios and i have few doubts, that doubts will be helpful for fresher like me so understand In app purchase.
1)I got problem in my app if user "install my app into new device or same device if he remove my app before" at that time when user try to buy already purchase item my code does not callrestoreTransaction in switch case of updatedTransactions
I get the message that you ve already purchased this tap okay to downlaod it FREE Enviornment sandbox and it call the SKPaymentTransactionStatePurchased case but it does not call the SKPaymentTransactionStateRestored what will the problem in my case..
So i have implementing separate Restore Button to restore all video item already brought by user so just need to know that will it reject my app at apple store?
2) For purchase of item it ask me password only one time and after that it doesn't ask me for password to purchase. it directly display the dialog box with confirm button but my project manager says it should ask for password for every item purchase.
It ask for password every time when i try to restore the Purchase..strange.
3) Currently i am testing in sandbox when i try to purchase with real apple id it display purchase failed(i have to use test account to test purchase as apple document says) but my project manager says that it should ask for new test username if you are testing in sandbox (as document said you have to sign out from setting manually but my project manager want to it should do automatically) ,
So just need to ask that is it possible to sign-out and display sign box by coding ( i know its not possible but for information i ask)
4)Currently my app is working in sandbox environment but do i need to change something for real purchase for my app?..or apple will automatically change sandbox to real purchase when apple validate my app and sign it and available on app store?
5)i am validating transaction on my own server so i am sending sandbox 1 if i am on sandbox environment otherwise i have to send 0 ( currently i hardcode sandbox value to 1)so is there any method to detect environment is sandbox or real?
Here is my purchase code and Restore button code
any help is appreciated
Purchase code
- (IBAction)PaymentButton:(id)sender {
loadingHUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
loadingHUD.labelText = NSLocalizedString(#"Loading", nil);
[loadingHUD show:YES];
[self startPurchase];// call the restore Purchase method
//[loadingHUD showWhileExecuting:#selector(startPurchase) onTarget:self withObject:nil animated:YES];// call the restore Purchase method
}
- (void)startPurchase {
if([SKPaymentQueue canMakePayments]) {
NSLog(#"IN-APP:can make payments");
[self requestProductData];
}
else {
NSLog(#"IN-APP:can't make payments");
loadingHUD.hidden=YES;
}
}
- (void)requestProductData {
NSLog(#"IN-APP:requestProductData");
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject:myIdentifier]];
request.delegate = self;
[request start];
NSLog(#"IN-APP:requestProductData END");
NSLog(#"Productdata is %#",myIdentifier);
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
#try {
SKProduct *product = [response.products objectAtIndex:0];
SKPayment *newPayment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:newPayment];
NSLog(#"IN-APP:productsRequest END");
}
#catch (NSException *exception) {
// Failed to purchase Hide the progress bar and Display Error Dialog
loadingHUD.hidden=YES;
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Alert" message:#"Error in Product id can not purchase" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
}
}
- (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];
default:
break;
}
}
}
- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
NSLog(#"Transaction Completed");
// Finally, remove the transaction from the payment queue.
[self verifyReceipt:transaction]; // Call the verifyReceipt method to send transaction.bytes
NSLog(#"Purchase Transaction finish");
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void) restoreTransaction: (SKPaymentTransaction *)transaction
NSLog(#"Transaction Restored %#",transaction.originalTransaction.payment.productIdentifier);
// You can create a method to record the transaction.
// [self recordTransaction: transaction];
loadingHUD.hidden=YES;
// You should make the update to your app based on what was purchased and inform user.
// [self provideContent: transaction.payment.productIdentifier];
// Finally, remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void) failedTransaction: (SKPaymentTransaction *)transaction
{
loadingHUD.hidden=YES;// hide loadingHUD
if (transaction.error.code != SKErrorPaymentCancelled)
{
// Display an error here.
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Purchase Unsuccessful"
message:#"Your purchase failed. Please try again."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
For restore it simple
-(void)startRestore
{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
if ([queue.transactions count] == 0)
{
HUD.hidden=YES;
UIAlertView *restorealert = [[UIAlertView alloc]
initWithTitle:#"Restore"
message:#"There is no products purchased by you"
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[restorealert show];
}
else
{
NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
NSString *temp = transaction.payment.productIdentifier;
NSString *testID = [temp stringByReplacingOccurrencesOfString:projectIdString withString:#""];
NSString *productID = [testID stringByReplacingOccurrencesOfString:#"." withString:#""]; // remove Dot
NSLog(#"cutted string is %#",productID);
[purchasedItemIDs addObject:productID];
NSLog(#"** Purchased item is %#",purchasedItemIDs);
}
HUD.hidden=YES;
HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
HUD.labelText = NSLocalizedString(#"Restoring", nil);
[HUD showWhileExecuting:#selector(restorePurchasedItem) onTarget:self withObject:nil animated:YES];// call the restore Purchase method
}
}
IMHO you'd better crop this question in some number if different questions.
Anyway, I'll try to answer:
1) That's how it meant to be.
You get transactions with SKPaymentTransactionStateRestored state only if you call restoreCompletedTransactions manually. AFAIK, that's the normal practise to create one button (for example, "Restore purchases") for that.
2) Can't say anything about that. Normally, every time, when your app is going to make a purchase (and take some user's money), it should ask user for password.
3) AFAIK, no. You work with Apple servers through iTunes/AppStore apps. It's their business to remember user's iTunes account. And I don't think they give you any way to make current user logged out. Your project manager should understand it :-)
4), 5) There is no difference in production/sandbox environments until you try to verify the receipt. If you talk about work with server, I hope, you use server to verify receipts.
At device's side, all what you do is working with StoreKit framework. You don't define any URLs to Apple servers, you just use framework's classes and call it's methods. AFAIK, you don't need to make any changes to your code for sandbox and production support at the same time.
But at your server's side, there is difference: when your app is in production already, you should send HTTP POST requests to https://buy.itunes.apple.com/verifyReceipt . At the other hand, when your app is still in the sandbox, use https://sandbox.itunes.apple.com/verifyReceipt .
How to handle that? Watch WWDC 2012 video 308 about Auto-Renewable subscriptions. They suggest 2 ways:
1) Smart server. When your app send receipts to your server, it also passes some parameter, to let server know, which Apple's server to use. As I see, you already use this method.
2) Reactive server. Your server always send receipts to Apple's production server. You check the response, and if the status is 21007 (following the documentation, "This receipt is a sandbox receipt, but it was sent to the production service for verification."), you send the same request to Apple's sandbox server.
Use this code, it might help you....
//To Show Alert of InAppPurchase
- (void)showAlert
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"" message:#"Click buy to
purchase full tracks and other functionalities of the application." delegate:self
cancelButtonTitle:nil otherButtonTitles:nil, nil];
[alert addButtonWithTitle:#"Buy"];
[alert addButtonWithTitle:#"Restore Transaction"];
[alert addButtonWithTitle:#"Cancel"];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:
(NSInteger)buttonIndex{
if(buttonIndex==0)
{
// For buy an item
[self Declarations];
}
else if(buttonIndex == 1)
{
//For Restore Previous Transaction
[self restorePreviousTransaction:nil];
}
else
{
//Do something here..
}
}
#pragma mark In App Purchase
-(void)Declarations
{
if ([SKPaymentQueue canMakePayments]) {
NSLog(#"parental functions are disabled");
SKProductsRequest *productRequest = [[SKProductsRequest
alloc]initWithProductIdentifiers:[NSSet
setWithObjects:#"com.tapmobi.careerandsuccess.inapp",nil]];
productRequest.delegate=self;
[productRequest start];
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
}
else
{
NSLog(#"parental functions are enabled");
}
}
- (IBAction)restorePreviousTransaction:(id)sender {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:
(SKProductsResponse *)response
{
SKProduct *validProduct=nil;
int count = [response.products count];
NSLog(#"number of prouducts present:%d",count);
if(count==0){
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
return;
}
validProduct = [response.products objectAtIndex:0];
NSLog(#"the product is :%#",validProduct.localizedTitle);
SKPayment *skpayment = [SKPayment paymentWithProduct:validProduct];
[[SKPaymentQueue defaultQueue] addPayment:skpayment];
[[SKPaymentQueue defaultQueue]addTransactionObserver:self];
}
-(void)requestDidFinish:(SKRequest *)request
{
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
}
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
NSLog(#"Failed to connect with error: %#", [error localizedDescription]);
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
NSString *message = [[NSString alloc]init];
BOOL bSuccess = NO;
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchasing:
NSLog(#"stuff is getting purchased");
break;
case SKPaymentTransactionStatePurchased:
NSLog(#"purchased properly");
message = #"Thank you.";
[[NSUserDefaults standardUserDefaults] setValue:#"Full Version"
forKey:PURCHASED_KEY];
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
bSuccess = YES;
break;
case SKPaymentTransactionStateRestored:
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
if (transaction.error.code != SKErrorPaymentCancelled) {
NSLog(#"error happened");
message = #"Purchase is not successfull. Try again later";
}
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
break;
default:
break;
}
}
if (bSuccess){
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"" message:message
delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alert show];
}
}
Happy Coding..

xcode webview error not working

This web view failed error showing the alert popup even when it is loading the website. I believe I need to delay this method in order for it to work.What will be the best method for doing this?
- (void)webView:(UIWebView *)webViewfail didFailLoadWithError:(NSError *)error
{
if([webViewfail isEqual:webview]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection Failed" message:#"Check your Internet connection before refreshing."
delegate:webview
cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}
Here is how I am loading the website
- (void)viewDidLoad
{
[webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.blabla.com"]]];
}
The problem is most likely an error -999 which generally happens when something from the webpage does not load correctly or a user tries to navigate back while the page is still loading. After some research here's what I found and used to keep the NetworkAlert from poping up every time but still popping up when there is no network.
-(void)webView:(UIWebView *)webBlog didFailLoadWithError:(NSError *)error{
if ([error code] != -999) {
NSLog(#"Could not load the dumb webPage");
//show error alert, etc.
[self showNoNetworkAlert];
}else{
NSLog(#"Could not load the dumb web page...just might blame user!");
}
}
- (void) showNoNetworkAlert{
UIAlertView *baseAlert = [[UIAlertView alloc]
initWithTitle:#"No Network" message:#"A network connection is required. Please verify your network settings and try again."
delegate:nil cancelButtonTitle:nil
otherButtonTitles:#"Dismiss", nil];
[baseAlert show];
}
Hope this helps someone...

Reachability False Positive

I'm getting false positives with my Reachability code. I get the UIAlert "No Internet Connection", when the network is available but not activated. Any help is very much appreciated.
//Check for Internet
Reachability* wifiReach = [[Reachability reachabilityWithHostName: #"www.apple.com"] retain];
NetworkStatus netStatus = [wifiReach currentReachabilityStatus];
switch (netStatus)
{
case NotReachable:
{
NSLog(#"Access Not Available");
break;
}
case ReachableViaWWAN:
{
NSLog(#"Reachable WWAN");
break;
}
case ReachableViaWiFi:
{
NSLog(#"Reachable WiFi");
break;
}
}
if (netStatus==NotReachable){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Internet Required" message:#"There is no internet connection. Please connect and try again" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
[alert release];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}else{
//Push Controller
}
Same answer as the previous question.
Reachability sometimes only gives the correct answer after your app, or some previous app, has tried to connect and waited for data. So you might as well just try to get data from your connection, as that may well give you a more correct answer more quickly than asking the Reachability API. Maybe just let the user decide an activity indicator has been spinning long enough.

open message composer by selecting phone no in contact list in iPhone

I stuck in issue with these.
I open default contact list and try to open message composer sheet by selection phone number of particular contact.
But it close my app and try to call that person instead.
What i am doing wrong?
Please let me know.
This is my code of delegate method of address book from which i try to open message box.
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
Class msgClass = (NSClassFromString(#"MFMessageComposeViewController"));
if (msgClass != nil)
{
// We must always check whether the current device is configured for sending messages
if ([msgClass canSendText])
{
[self showComposerforMessage];
[self dismissModalViewControllerAnimated:TRUE];
}
else
{
UIAlertView *objAlert=[[UIAlertView alloc]initWithTitle:#"" message:#"Device is not configured to send text message." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[objAlert show];
[objAlert release];
}
}
else
{
UIAlertView *objAlert=[[UIAlertView alloc]initWithTitle:#"" message:#"Device is not configured to send text message." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[objAlert show];
[objAlert release];
}
return TRUE;
}
Return NO there:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
You return YES and iOs thinks thet you are trying to call from your application.

App crashes after exiting Mail Composer

My app is crashing shortly after closing a MFMailComposeViewController. A UIWebDocumentView is releasing, which deallocates a ComposeBodyField object and it crashes on objc_msgSend. It only happens some of the time, and only on old devices. I'm assuming that something is being release/cleaned up before it's supposed to, so when the message is sent, the object doesn't exist.
The problem is that I can't get anymore information than that, and I have no idea how any of it ties together. If anyone can shine some light on this, it would be great.
I've had similar problems with crashing after dimissing the MFMailComposer. After removing the [myMailComposer release] everything is fine. I'm sure I'm following the rules for memory management since it's fine all over in the app except at this specific place. Now my "Build & Analyze" nags about it, but the app is perfectly stable.
Please try this code that works for me.
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
switch (result)
{
case MFMailComposeResultCancelled:
{
break;
}
case MFMailComposeResultSaved:
{
break;
}
case MFMailComposeResultSent:
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Email" message:#"Email Sent" delegate:self cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
[self performSegueWithIdentifier:#"backHome" sender: self];
break;
}
case MFMailComposeResultFailed:
{
NSLog(#" Failed");
break;
}
default:
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Email" message:#"Email Failed" delegate:self cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
}
break;
}
}