I have successfully implemented IAP in ios sdk.
But the problem is when user click on Restore purchase button I initiate the restore by this code:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
And also I have written this code to handle and initiate the purchase:
- (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
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
[SVProgressHUD dismiss];
[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
break;
case SKPaymentTransactionStateRestored:
[SVProgressHUD dismiss];
NSLog(#"Transaction state -> Restored");
//add the same code as you did from SKPaymentTransactionStatePurchased here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[SVProgressHUD dismiss];
//called when the transaction does not finish
if(transaction.error.code == SKErrorPaymentCancelled){
[SVProgressHUD dismiss];
NSLog(#"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
When I select Restore then A dialogs come which ask for password. When I cancel it The progress hud doesn't hides.
How can I do this. How to update UI when user cancel purchase in between the process.
Here is the image.
EDIT now when i use the delegate method
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
[SVProgressHUD dismiss];
}
When It asks for password the SVProgressHUD hides. No matter I press cancel or OK. How to handle this.
And How to update the UI and continue Purchase when user enters correct password.
Your delegate should implement this method:
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
The error in this case will indicate a cancel action.
Related
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.
I implemented IAP in my app with this code:
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
[_loadingIndicator startAnimating];
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[self unlockPurchase];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self unlockPurchase];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Transaction failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
default:
break;
}
}
}
When purchase button is pressed and an alert view pops up and asks for Apple ID and password, if user presses Cancel button, the code goes into SKPaymentTransactionStatePurchased: case and the app unlocks the product. What is wrong with the code?
I found what was going on. Local IAP Store tweak from Cydia was running.
I installed it to check if it is possible to bypass ITunes Store with this tweak and find a workaround to prevent it and then I forgot to turn it off.
I used info from this brilliant page to add in-app purchases.
However I get the NSLog No Product Available. I have checked to make sure I have added the correct Product ID. I am at a loss to what to do and have been at this for several hours.
This is for a game built on Cocos2d and I can't figure out what is wrong. Sorry for the abundance of code.
I have added <SKProductsRequestDelegate, SKPaymentTransactionObserver> and <StoreKit/StoreKit.h>
.m
#define kRemoveAdsProductIdentifier #"COINS1000"
#implementation shopCoins
- (void)buy500Coins{
NSLog(#"User requests to remove ads");
if([SKPaymentQueue canMakePayments]){
NSLog(#"User can make payments");
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kRemoveAdsProductIdentifier]];
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){
validProduct = [response.products objectAtIndex:0];
NSLog(#"Products Available!");
[self purchase:validProduct];
}
else if(!validProduct){
NSLog(#"No products available");
//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];
}
- (IBAction) restore{
//this is called when the user restores purchases, you should hook this up to a button
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
if(SKPaymentTransactionStateRestored){
NSLog(#"Transaction state -> Restored");
//called when the user successfully restores a purchase
[self doRemoveAds];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (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");
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;
}
}
}
- (void)doRemoveAds{
NSLog(#"Bitches");
}
If it's a debug build you probably need to add a sandbox user on iTunes connect this will allow you to use IAP that have not been reviewed yet. Note this user should match your iTunes connect account.
Anybody know how to make restore option using IAP.
I use non consumable product for purchase.
I know that I have to implement this delegate methods:
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
but I still can not figure out with a process invoke this method.
I assume that I need to invoke this method [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; before I get invoke callback method.
Can you explain step by step how it work.
You assume right! The only thing you have to invoke is:
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
This restores all the completed transactions that the user has made. For each transaction this SKPaymentTransactionObserver method is called (the same method is also called each time a user makes a purchase):
- (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;
}
}
}
Using the transactionState you can distinguish whether the transaction was a original purchase (SKPaymentTransactionStatePurchased) or a restore (SKPaymentTransactionStateRestored) if you need to do that.
If you need to know when the restore is finished you can use:
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
NSLog(#"%d items restored", queue.transactions.count);
}
iOS In-App purchases: When and why does SKPaymentTransactionStateRestored get sent back as status of a transaction?
Does it related to having a recurring subscription plan?
You get SKPaymentTransactionStateRestored transactions instead of SKPaymentTransactionStatePurchased transactions when you are restoring the user's purchases by sending a restoreCompletedTransactions message to the SKPaymentQueue.
General rule is to process both callbacks: for purchase and restore purchase.
- (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
{
[self recordTransaction: transaction content:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{
[self recordTransaction: transaction content:transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}