in app purchase always gives an error message in iphone - iphone

I have implemented in app purchase in my application.On clicking the buy button it will asks for the confirmation.But after clicking ok,it always shows an error message saying "You have already purchased this but it hasn't been downloaded.Tap ok to download it now." Nothing happens after this.In the
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{}
it always call the case "SKPaymentTransactionStateFailed:".itried to print the error and it says "unable to connect to itunes store".What i have done is,on clicking the buy button,calls this method:
`- (void) requestProductData
{
NSLog(#"requestProductData");
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers:
[NSSet setWithObjects:featureBusiness , nil]] ; // add any other product here
request.delegate = self;
[request start];
if([delegate respondsToSelector:#selector(beginAnimationPurchased)]) {
[delegate beginAnimationPurchased];
}
}
`
can anyone guide me?thanks in advance.

I had the same problem. I was using the IAPHelper code from the Ray Wenderlich example, which I then subclassed into my own MyAppIAPHelper. I resolved this particular problem by declaring an observer at the top of my main view controller 'm' file:
IAPHelper *observer;
then adding a transaction observer in the ViewDidLoad of my main view controller:
observer = [[IAPHelper alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
In my dealloc, I added:
[[SKPaymentQueue defaultQueue] removeTransactionObserver:observer];
Not sure this is the best solution, but it worked for me. Hope this helps :-)

Related

Detect Cancel Button Tap for "Confirm Your In-App Purchase" UIAlert

I implemented in-app purchase based on this tutorial. The problem I experience is that I cannot detect when Cancel button is pressed on the "Confirm Your In-App Purchase" alert, which is a part of StoreKit framework.
Some sources suggest -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions is called when Cancel is pressed but in my case it never runs. My set up is the ViewController which imports IAPManager:NSObject class which conforms to SKProductRequestDelegate and SKPaymentTransactionObserver. Product is successfully being requested but transaction observer is never calling paymentQueue.
How can I make it work so I can detect Cancel button?
in delegate method i look at the tutorial failedtransaction does nothing if user cancels. but you can add it like that.
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
if (transaction.error.code != SKErrorPaymentCancelled)
{
// error!
NSLog(#"Something went Wrong!");
[self finishTransaction:transaction wasSuccessful:NO];
NSLog(#"transaction error :%#", transaction.error.localizedDescription);
}
else
{
NSLog(#"Cancelled");
// this is fine, the user just cancelled
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
This line had to be added to make it work:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
Thanks all for your help.
I haven't used StoreKit, but I'm guessing that your SKRequestDelegate will receive the message request:didFailWithError: if the user cancels.

In App Purchase sandbox not prompting for my login/pass

We're developing an app which (ofcourse) uses in app purchases (IAP).
I've done everything in the guide to enable iap and everything works fine, untill I want to make purchase.
Some of the code:
MainViewController.m
-(void)viewDidLoad {
if ([SKPaymentQueue canMakePayments]) {
MyStoreObserver *observer = [[MyStoreObserver alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObjects: #"com.company.app.product1", #"com.company.app.product1", nil]];
request.delegate = self;
[request start];
}
};
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
for (SKProduct *prod in response.products) {
NSLog(#"%# (%#)", prod.localizedTitle, prod.price);
}
[request release];
};
-(IBAction)clickBuy:(UIButton *)__sender {
SKPayment *payment = [SKPayment paymentWithProductIdentifier:#"com.company.app.product1"];
[[SKPaymentQueue defaultQueue] addPayment:payment];
};
MyStoreObserver.m
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
NSLog(#"SKPaymentTransactionStatePurchased");
break;
case SKPaymentTransactionStateFailed:
NSLog(#"SKPaymentTransactionStateFailed");
break;
case SKPaymentTransactionStateRestored:
NSLog(#"SKPaymentTransactionStateRestored");
break;
case SKPaymentTransactionStatePurchasing:
NSLog(#"SKPaymentTransactionStatePurchasing");
default:
break;
}
}
};
The productRequest: delegate method shows 2 products with their name / price. Like I entered in the iTunes connect site.
But once I click the 'buy' button, no dialog pops up or asks me for my credentials. Only "SKPaymentTransactionStatePurchasing" is logged.
And I:
- ... have logged out in the settings/store pane
- ... am using the right provisioning profiles
- ... am desperate
Anyone?
I encountered a similar problem, but mine was more of a boneheaded move on my part. I had 'refactored' the call to finishTransaction so that it was being called for every state in transactionState:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
// do stuff
break;
case SKPaymentTransactionStateFailed:
// do stuff
break;
case SKPaymentTransactionStateRestored:
// do stuff
break;
default:
break;
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
Turns out™, this will also call finishTransaction on SKPaymentTransactionStatePurchasing, which will cause a crash. Moving finishTransaction back into each case of the switch statement fixed that.
After Pulling my hair out in frustration with a similar problem (instead of not being asked for my credentials it was automatically filling in the email address without the option to change it even when logged out of the store in the settings app). I discovered that I had a failed transaction stuck in the queue from development builds on the same device, I had to clear all of the transactions in the queue on the device and then try to test again.
NSArray *transactions = [[SKPaymentQueue defaultQueue] transactions];
for(id transaction in transactions){
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
I hooked this code upto an IBOutlet and after being run once my in app purchases worked.
I also had been pulling my hair out a bit with this. Turns out a simple reboot of the test device got everything working fine.
An iPhone can be restricted from accessing the Apple App Store. For example, parents can restrict their children’s ability to purchase additional content.
Before placing transaction make sure, can you u buy or not?Check it like this -
-(IBAction)clickBuy:(UIButton *)__sender {
if ([SKPaymentQueue canMakePayments]) {
SKPayment *payment = [SKPayment paymentWithProductIdentifier:#"Product_id"];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
else {
//show appropriate message
}
}
Are you testing on iPhone/iPad Simulator 4.2 or something? That might be the problem. Testing on iPhone/iPad Simulator 5.0, or the device, will run storekit correctly.

updatedTransactions failed to be called back after iOS 4.2

We've been having a tricky problem with store kit. We are trying to implement correctly the in-app purchase renewable subscriptions process into an app that already is on the appStore with standard in-app purchase (non-consumable products) set up.
So far, and after spending 2 months fighting with the sandbox weird behavior, we came up with a working solution on a test iPad running iOS4.2.
Nasty surprise we got when testing this code on a iPad with 4.3 or 5.0, it does not have the same behavior.
We narrowed it down to this simple fact:
- iOS4.2 : the updatedTransactions callback is working properly
- iOS4.3 and above: the updatedTransactions callback is never called by the sandbox.
Any ideas on why a store kit code that works on iOS4.2 wouldn't work on following iOS versions? I didn't see anything deprecated on this.
Here is the code of our updatedTransactions code:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
NSLog(#"Add payment queue");
for(SKPaymentTransaction *transaction in transactions) {
NSLog(#"Transaction state: %d, %d, %d, %d", transaction.transactionState, SKPaymentTransactionStatePurchased, SKPaymentTransactionStateFailed, SKPaymentTransactionStateRestored);
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
if([transaction.payment.productIdentifier isEqualToString:FM_PRODUCT_IDENTIFIER_SUBSCRIPTION]){
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:transaction.transactionReceipt forKey:#"TransactionReceiptOfTransaction"];
}
[self completeTransaction:transaction];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"%#", transaction.error);
[self failedTransaction:transaction];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Error", #"")
message:NSLocalizedString(#"Your subscription has expired.", #"")
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
SAFE_RELEASE(alert);
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
if([transaction.error code] != SKErrorPaymentCancelled) {
if([transaction.payment.productIdentifier isEqualToString:FM_PRODUCT_IDENTIFIER_SUBSCRIPTION]){
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:transaction.transactionReceipt forKey:#"TransactionReceiptOfTransaction"];
}
}
break;
case SKPaymentTransactionStateRestored:
if([transaction.payment.productIdentifier isEqualToString:FM_PRODUCT_IDENTIFIER_SUBSCRIPTION]){
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:transaction.transactionReceipt forKey:#"TransactionReceiptOfTransaction"];
}
[self restoreTransaction:transaction];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
default:
NSLog(#"Other");
break;
}
}
}
Thanks,
Stephane
I had exactly the same problem and to solve it I switched the source file which contains the
addTransactionObserver call to ARC = none.
// StoreKit
CustomStoreObserver *observer = [[CustomStoreObserver alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
Seems that ARC or StoreKit framework can't work well together.
To switch a source file with the ARC = none, I set -fno-objc-arc flag as Josh Caswell says it in the "Disable Automatic Reference Counting for Some Files" question.
The answer is to add -fno-objc-arc to the compiler flags for the files you don't want ARC. In Xcode 4, you can do this under your target -> Build Phases -> Compile Sources.
Update: My explanation about that behavior is that ARC wants to manage the memory itself but it does not do it well for Storekit. ARC releases too early the 'observer' object. That cause the crash when the InAppPurchase module (Apple side) wants to advise your "updatedTransactions"
method. And for me to stuck 'observer' object in memory during the life of my application. I decided to give it the property retain in my .h file.

reloadData after Login on iPhone app

I have a Login that is initiated on the viewDidLoad of the mainApp. This means numberOfRowsInSection in the mainApp runs before the Login and results my tableview not being populated.
I have my JsonArray in the viewWillAppear in the mainApp the reloadData function to make numberOfRowsInSection run after the Login as well. So I put:
[self.tableView reloadData];
...in the viewWillAppear. But this doesn't make it run after Login.
Any suggestions?
Thanks in advance.
CODE UPDATE
Login Page
if (serverOutput != nil) {
//UIAlertView *alertSuccess = [[UIAlertView alloc] initWithTitle:#"Congrats" message:#"You are authorised" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
//[alertSuccess show];
//[alertSuccess release];
LucentaAppDelegate *appDelegate = (LucentaAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.userProducts = serverOutput;
loginButton.enabled = FALSE;
[self dismissModalViewControllerAnimated:YES];
Main View
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
LucentaAppDelegate *appDelegate = (LucentaAppDelegate *)[[UIApplication sharedApplication] delegate];
self.jsonArray = [appDelegate.userProducts JSONValue];
//[self.tableView reloadData];
[self performSelector:(#selector(refreshDisplay:)) withObject:(tableView) afterDelay:0.5];
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Test Alert" message:#"View Will Appear." delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles: nil];
[alert show];
[alert release]; }
-(void)refreshDisplay:(UITableView *)tableView {
[self.tableView reloadData];}
When your login (started in viewWillLoad) is completed, you should get some delegate callback call that tells you that your request as completed. You usually specify the delegate when creating the request.
If you are using NSURLRequest and NSURLConnection, the delegate is specified in one of:
+ connectionWithRequest:delegate:
– initWithRequest:delegate:
– initWithRequest:delegate:startImmediately:
depending on how your create the request, and the delegate callback to override is connectionDidFinishLoading: (or connection:didFailWithError: in case of communication errors).
connectionDidFinishLoading: is the right place where you should issue your [self.tableView reloadData];, after having updated your table data source.
If you are not using NSURLRequest, then the delegate callback will be different.
EDIT:
if your mainView receive the JSon data it needs, what you should do there is update your table data source and call reloadData on the table.
Indeed, calling reloadData will force calling numberOfRowsInSection again, but this information is provided by your data source, so if it is not updated to reflect the new data you have received, it will always reflect the old value.
Could you provide more information about your data source?
That depends on the actual code of your app. I suggest just to put a method like
- (void)loginSucceeded {
[self.tableView reloadData];
}
to your view controller and call it after the login was successful. It is also convenient as you might need to do some more complicated actions in this situation when your app grows.

iPhone - SKProductsRequest and "message sent to deallocated instance"

I got troubles implementing InAppPurchase. My implementation of purchase is in modal view controller (AppUpgradeViewController), that I present from another modal view. I do it like this:
AppUpgradeViewController * appUpgradeViewController = [[AppUpgradeViewController alloc] init];
appUpgradeViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
appUpgradeViewController.delegate = self;
[self presentModalViewController:appUpgradeViewController animated:YES];
[appUpgradeViewController release];
Then, in my upgrade view I do the following:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
NSSet *productIdentifiers = [NSSet setWithObject:kInAppPurchaseProUpgradeProductId];
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
self.productsRequest.delegate = self;
[productsRequest start];
Then I have implemented
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
where I do:
[self.productsRequest release];
and then I have other required methods.
The problem is when I show modal, and quickly dismiss it then after few seconds i got the following on console (I turned on NSZombieEnabled):
*** -[AppUpgradeViewController respondsToSelector:]: message sent to deallocated instance 0x2e91f0
I suppose that it's something with that product request, but I don't know how to debug or fix it. It seems that the answer for request comes to this controller just after it's dismissed (and deallocated), but I don't know how to prevent it from receiving messages after dismiss/dealloc.
Thanks for any help!
I had the same problem. Not with a modal view, but with a pushed view on the navigation controller stack. When I quickly navigated back before my product information was loaded via SKProductsRequest, it also throws me an message sent to deallocated instance exception. I solved this by calling the cancel method (see reference) on my SKProductsRequest object.
Additional to this I also set the delegate to nil.
This is my product request:
NSSet *productIdentifiers = [NSSet setWithObject:#"MY_IDENTIFIER"];
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
productsRequest.delegate = self;
[productsRequest start];
and this is what I called in the dealloc method to cancel it.
productsRequest.delegate = nil;
[productsRequest cancel];
productsRequest = nil;
I also removed the observer from the SKPaymentQueue like described in this answer for another question.
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
You probably forgot to nil your request delegate in AppUpgradeViewController's dealloc:
- (void)dealloc {
...
productsRequest.delegate = nil;
[productsRequest release], productsRequest = nil;
...
[super dealloc];
}
I guess that's because you have released your productsRequest, but it seems you haven't set the pointer to nil which means it's still pointing at the now-invalid memory location.
How is the productsRequest property defined ? If it has the retain option, then instead of:
[self.productsRequest release];
you need to do:
self.productsRequest = nil; // Property will do the release for you.
If it has the assign option, then you need to do:
[self.productsRequest release];
self.productsRequest = nil; // Or else some might access this pointer,
// which now might point to nirvana.
Is it because you're doing this:
[appUpgradeViewController release];
too early?
Try doing it in the dealloc method of whatever class you're allocing it in.
Providing you're not allocing it more than once, of course. This would also require you to move your declaration into the class header.
Swift 3
It's a good idea to close the request if you started it. This is a safe way to do it in Swift.
// strong reference at top of class
var productRequest: SKProductsRequest!
// at some point you will fetch products
// on deallocating the window
if productRequest != nil {
productRequest.cancel()
productRequest.delegate = nil
}