in-app purchase being restored even before purchase - ios

An update of my existing app has been published on the app store today with in-app purchase to remove ads. I had to put a restore button as it is non-consumable in-app purchase. i have noticed that i can restore the in-app purchase without even purchasing it.
The app went for sale about 4 hours ago. Is this normal for a new app? does the app store take some time to hook up the in-app purchase (probably a silly question)? or have i done something wrong in my code?
Please response if you know whats going on.
EDIT:
Here is the code i used
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
default:
break;
}
}
}
- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
[[SKPaymentQueue defaultQueue]addTransactionObserver:self];
}
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
if (SKPaymentTransactionStatePurchased)
{
//save purchase
NSUserDefaults *savePurchase = [NSUserDefaults standardUserDefaults];
[savePurchase setBool:TRUE forKey:k_Save];
[savePurchase synchronize];
_adBanner.hidden = YES;
// alert after successful restore.
UIAlertView *restoreTransactionAlert = [[UIAlertView alloc] initWithTitle:#"Congratulations!"
message:#"Your purchase is restored."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[restoreTransactionAlert show];
}
else
{
// alert after unsuccessful restore.
UIAlertView *restoreTransactionAlert = [[UIAlertView alloc] initWithTitle:#"Restore failed"
message:#"Restore failed. please try again"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[restoreTransactionAlert show];
}
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
}

Your implementation for restoring transactions is incorrect. Your condition in the -restoreTransactionsCompleted method, i.e.
if (SKPaymentTransactionStatePurchased)
Will always return true, as SKPaymentTransactionStatePurchased is an enum with value 1.
Look up on the documentation on restoring transactions here.

Related

Purchasing multiple consumables at a time

I am allowing user to purchase multiple consumables(of same type) at a time. I have implemented the following code:
- (void)purchaseMyProduct:(NSArray *) products {
if ([SKPaymentQueue canMakePayments]) {
for(SKProduct *product in products) {
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
else {
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:
#"Purchases are disabled in your device" message:nil delegate:
self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
}
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
UIAlertView *alertView ;
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(#"Purchasing");
break;
case SKPaymentTransactionStatePurchased:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
[self
break;
default:
break;
}
}
}
But the problem is that for each single consumable entry a separate prompt is displayed to ask user to confirm the purchase.
Is it possible in IAP to purchase multiple consumables of same type at a time with one prompt for user?
One logic which I have thought is to create separate products in store for multiple consumables e.g. one product for two and another for three consumables.
Kindly help.
Thanks,
If they are of the same type, you should use the quantity field. Check out the SKPayment class reference. The maximum value for quantity is 10.
To create a SKPayment object with a quantity greater than 1, create a SKMutablePayment object, adjust its quantity property and then add it to the payment queue.
Example from the docs:
SKMutablePayment *myPayment = [SKMutablePayment paymentWithProduct: myProduct];
myPayment.quantity = 2;
[[SKPaymentQueue defaultQueue] addPayment:myPayment];

iOS in-app restore purchases nightmare

I've read ALL the threads etc. on the internet and still am getting back zero restored transactions when I try to restore purchased products (sandbox environment).
I have created 3 Non-renewing products and 2 sandbox testers through iTunes connect.
I have had zero issues setting up the ability to actually purchase products; all is well.
I have created a "restore purchases" button and am trying to get it to work. I've been testing the following: install app and make purchase. Purchase again and it asks if I want to renew. Delete app and log out of app store. Reinstall app and press restore purchases ... zero!!!
Here's some code I'm using:
-(void)viewDidLoad
{
[super viewDidLoad];
//in-app purchase setup
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
-(void)viewDidAppear:(BOOL)animated
{
//make request for in-app products
if([SKPaymentQueue canMakePayments])
{
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObjects:#"product1_id",#"product2_id",#"product3_id",nil]];
request.delegate = self;
[request start];
}
else
{
//alert user
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"..." message:#"Please enable In App Purchase in settings" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}
-(IBAction)restoreTransactionsPressed
{
_refreshRequest = [[SKReceiptRefreshRequest alloc] init];
_refreshRequest.delegate = self;
[_refreshRequest start];
}
-(void)requestDidFinish:(SKRequest *)request
{
if([request isKindOfClass:[SKReceiptRefreshRequest class]])
{
NSLog(#"Got a new receipt... %#",request.description);
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
}
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
NSLog(#"%#",error.description);
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
NSLog(#"updatedTransactions");
for(SKPaymentTransaction *transaction in transactions)
{
NSLog(#"updatedTransaction:%# ... state:%#",transaction.originalTransaction.payment.productIdentifier,transaction.transactionState);
switch(transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
NSLog(#"SKPaymentTransactionStatePurchased");
//unlock feature code
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"SKPaymentTransactionStateFailed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(#"SKPaymentTransactionStateRestored");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
default:
break;
}
}
}
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(#"paymentQueueRestoreCompletedTransactionsFinished");
NSLog(#"received restored transactions: %i", (int)queue.transactions.count);
for(SKPaymentTransaction *transaction in queue.transactions)
{
NSLog(#"transaction:%#",transaction);
}
}
-(void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
NSLog(#"restoreCompletedTransactionsFailedWithError %#",error);
}
Why do I always get back zero restored transactions?
it seems that "restoreCompletedTransactions" isn't in turn calling - paymentQueue:updatedTransactions: if that helps.
I'm unsure that SKReceiptRefreshRequest is necessary, but I've tried both refreshing the receipt before restoring and not refreshing the receipt before restoring.
Please don't post links to thread. I've investigated this for a few Days now! Some people claim that Apple's Sandbox had issues in the past ... could this be the reason? tyvm in advanced; all help is appreciated!
According to Apple's documentation, you can't restore non-renewing subscriptions. Also, have a read here for a more detailed explanation.

In app purchase not working when moderator review my app

I am working on IAP first time. I have created test user and working good in sandbox env. I can see my Purchase and buy it as well(And of course i added StoreKit framework.), but problem is that: when i upload update with IAP to AppStore - moderator has reject my app, here is a reason:
We found that while you have submitted In App Purchase products for your app, the In App Purchase functionality is not present in your binary. Please see the attached screenshot/s for more information.(there is no some Purchase on the screen, empty space instead)
If you would like to utilize In App Purchase in your app, you will need to upload a new binary that incorporates the In App Purchase API to enable users to make a purchase
I just don't understand how it's possible? Why when i testing IAP - it's works fine, but it doesn't work when moderator doing it? All IAP have "Cleared for Sale" and "Waiting for review" status
Now if i upload app to the store will IAP works? Or maybe i need to do some additional action before?
Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
_iapArray = [[NSMutableArray alloc] init];
iap1 = #"com.mypurchase.addcoins1";
[self paymentCheck];
}
- (void)viewDidDisappear:(BOOL)animated
{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
- (void)paymentCheck{
if([SKPaymentQueue canMakePayments]){
NSLog(#"User can make payments");
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObjects:iap1, nil]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(#"User cannot make payments due to parental controls");
//this is called the user cannot make payments, most likely due to parental controls
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
SKProduct *validProduct = nil;
int count = [response.products count];
if(count > 0){
_iapArray = response.products;
indicator.hidden = YES;
[self.tableView reloadData];
}
else if(!validProduct){
NSLog(#"No products available");
UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Error"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[myAlertView show];
//this is called if your product id is not valid, this shouldn't be called unless that happens.
}
}
- (IBAction)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
switch (transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(#"Transaction state -> Purchasing");
//called when the user is in the process of purchasing, do not add any of your own code here.
break;
case SKPaymentTransactionStatePurchased:
//this is called when the user has successfully purchased the package (Cha-Ching!)
// [self doRemoveAds]; //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"Transaction state -> Purchased - %#", transaction.payment.productIdentifier);
if ([transaction.payment.productIdentifier isEqualToString:iap1]) {
[self addCoins:5];
}
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Transaction state -> Restored");
//add the same code as you did from SKPaymentTransactionStatePurchased here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
//called when the transaction does not finnish
if(transaction.error.code != SKErrorPaymentCancelled){
NSLog(#"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
As per their reply, you have integrated IAP in application but you have not used it. So, can you please make sure you have properly integrated IAP in application ? Please test in same Device and iOS if they mentioned.

IAP Restore button

I'm using a non-consumable IAP and trying to get the Restore button working properly.
I've noticed that when a user try's to buy something they've already purchased, an Apple alert comes up saying "You've already purchased this. Would you like to get it again for free."
So I was wondering how I could use that same exact receipt verification method/code for my Restore button?
I can't seem to find the code for it. I've looked in paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions and other places?
Can you please help? Thanks!
UPDATE
- (IBAction)purchaseRestore:(id)sender {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue]restoreCompletedTransactions];
// Stack
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
NSLog(#"4 IBAction Purchase Restore Method");
}
UPDATE 2
I can't tell if I'm doing this right, doubling up on some code, or adding unneeded code.
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(#"2 Payment Queue updatedTransactions - needs to verify logged in user: Purchasing Product From Store!");
break;
case SKPaymentTransactionStatePurchased:
if ([transaction.payment.productIdentifier
isEqualToString:INAPP_PRODUCT_ID_3]) {
NSLog(#"3 Payment Queue updatedTransactions - Product Purchased From Store!");
//Not sure if I need this
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:
#"3 Payment Queue updatedTransactions - Seems to to this twice: Purchase is completed succesfully" message:nil delegate:
self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
[self saveTransactionReceipt:transaction];
NSLog(#"3 Payment Queue updatedTransactions - Save Transaction Receipt: Called after product purchased");
[self saveValue:transaction];
NSLog(#"3 Payment Queue updatedTransactions - Save Value: Called after product purchased");
[self showButtonThree];
NSLog(#"3 Payment Queue updatedTransactions: will Show button Three now");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:#"purchasedThree"];
NSLog(#"updatedTransactions Yes for purchasedTeamThree: %hhd", [defaults boolForKey:#"purchasedThree"]);
[defaults synchronize];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"3 Payment Queue finishTransaction: Run");
}
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Restored");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
purchaseButton.enabled = YES;
NSLog(#"2 Payment Queue updatedTransactions: Purchase failed ");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
default:
break;
}
}
}
Your question is actually very useful to me too, since you made me realize that probably the app I currently have pending in the approval process of the AppStore will be rejected because I didn't implement a Restore button (I just added a label saying that pressing again the Buy button would restore previous purchases).
The correct way to do the restore process looks to implement a button calling this method:
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
and after that, of course, restoring the app functionality restricted to paying users.
Hope this helps.

In App Purchase - No purchase to restore

I'm trying to figure out if I get a message back when a user tries to restore an In App Purchase but there was no purchase ever made.
Right now, as soon as a user taps the restoreButton, I disable the restoreButton.
- (IBAction)purchaseRestore:(id)sender {
NSLog(#"4 IBAction Purchase Restore Method: start");
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue]restoreCompletedTransactions];
NSLog(#"4 Purchase Restore: SKPayment Queue two lines... log in user");
restoreButton.enabled = NO;
NSLog(#"4 Restore button enabled: No");
}
So if they click the restoreButton, and have nothing to restore then the restoreButton just stays grayed out.
I would like to do something like a UIAlert or change the restoreButton text if that happens to say "You don't have any items to Restore", but I assume I need to get some message back from Apple saying "no items to restore" so I can fire off that code.
Here's my updatedTransactions code if needed:
case SKPaymentTransactionStateRestored:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"5 Finish Transaction");
restoreButton.hidden = YES;
NSLog(#"5 restore button hidden: Yes");
[self showButtonThree];
NSLog(#"5 Show Button Three");
NSLog(#"5 Restored: End");
break;
Any ideas? Thanks!
Update
Tried this:
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
NSLog(#"Completed Transactions Finished");
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:
#"Restored succesfully" message:nil delegate:
self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
}
But that was popping up whether there was a transaction to restore or not. I had assumed this would get called if there was no transaction to restore maybe, but makes sense that it wouldn't:
-(void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
NSLog(#"Completed Transactions Failed with Error");
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:
#"Not restored succesfully" message:nil delegate:
self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
}
From the official Apple documentation for restoreCompletedTransactions
After the transactions are delivered, the payment queue calls the
observer’s paymentQueueRestoreCompletedTransactionsFinished: method.
If an error occurred while restoring transactions, the observer will
be notified through its
paymentQueue:restoreCompletedTransactionsFailedWithError: method.

Resources