I check & process IAP receipts (of consumables) on server side, and only call [[SKPaymentQueue defaultQueue] finishTransaction:transaction] on the app when it gets an okay back.
When the server does not return this okay (for whatever reason), the app correctly won't finish the transaction.
My question now is: Is there anything the app needs to do to receive the receipt again for a retry, or does iOS take care of this by invoking - (void)paymentQueue:(SKPaymentQueue*)queue updatedTransactions:(NSArray*)transactions`? When I reran my app, the item was re-submitted; but a user should not need to restart the app.
Related question: When I tried to buy a still pending item again (on the sandbox), I got an iOS alert saying that I bought it earlier but was not downloaded. Why is this? I would expect (and have actually seen) this for non-consumables. I could buy another consumable, with this previous one still pending.
Once you finish the transaction of CONSUMABLE IN-APP the following method will fire there add this statement "[[SKPaymentQueue defaultQueue] finishTransaction:transaction]" to remove the purchased product.Using this statement you can avoid the alert message(I bought it earlier but was not downloaded).
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
if (transaction.downloads)
{
[[SKPaymentQueue defaultQueue]
startDownloads:transaction.downloads];
} else {
//Add the following line to remove purchased observer
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
break;
case SKPaymentTransactionStateFailed:
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
break;
}
}
}
The next time your App adds a transaction observer the transaction will appear in the queue.
And perhaps this is why you get the 'not yet downloaded' message - that means 'the app store has not yet gotten a finishTransaction'.
Related
iam using this code on click on the item after init the buy process
SKPayment *payment = [SKPayment paymentWithProduct:self.skDollar_15] ;
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment] ;
it works fine in the first time but if i out of the screen and comeback again and try to perform this code again the application crash in this line
[[SKPaymentQueue defaultQueue] addPayment:payment] ;
The document say:
The payment request must have a product identifier registered with the
Apple App Store and a quantity greater than 0. If either property is
invalid, addPayment: throws an exception.
quantity is default to one, so check if your product identifier is valid not nil.
I am restoring completed transactions (recurring) with
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
and in
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
I got a list of history transactions restored, made by the app, but the method:
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
is not even called once, so I wonder which one should I use? I did some research and found that updatedTransactions: method should be used with checking transaction state, but if I got a list of transactions restored, it is just meaningless to treat them all as transactions. Which one should I use? Does paymentQueueRestoreCompletedTransactionsFinished only gives me the latest one (ie sandbox subscription expires in several minutes and I got a full list of history transactions made when testing, in updatedTransactions).
There is an excellent WWDC Video about using StoreKit, it is WWDC2012 Session 302.
To isolate each purchase, your updatedTransactions method could look something like this:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch(transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
// Unlock content
//... Don't forget to call `finishTransaction`!
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStatePurchasing:
// Maybe show a progress bar?
break;
case SKPaymentTransactionStateFailed:
// Handle error
// You must call finishTransaction here too!
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
// This is the one you want ;)
// ...Re-unlock content...
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
Once you've determined that the purchase is being restored, you can make content available as you see fit - preferably by calling a separate method from within that switch statement and passing the transaction as a parameter. The implementation is up to you of course.
Call [[SKPaymentQueue defaultQueue] addTransactionObserver:self] in (void)viewDidLoad or equivalent if applicable.
Then call [[SKPaymentQueue defaultQueue] restoreCompletedTransactions].
(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions will be called accordingly through (2).
If you don't call the method in (1), the application will never reach (3) to restore transactions in the first place.
I had this same issue with paymentQueueRestoreCompletedTransactionsFinished never getting called. I fixed by going to iTunes & Aoo Stores in Settings and logged out of the sandbox test account and tried it again. Worked as expected the next time after being prompted to login again on a restore.
From the Apple Docs:
paymentQueueRestoreCompletedTransactionsFinished: This method is called
after all restorable transactions have been processed by the payment
queue. Your application is not required to do anything in this method.
Sorry for that millionth question on autorenewable subscriptions, but i don't get it.
I've done everything as describet in Apples In-App Purchase Guidelines but it didn't solve the problem.
My problem is that i have created autorenewable subscriptions but they won't be autorenewed.
I've create a Payment Transaction Observer class, which implements the SKPaymentTransactionObserver interface. This class will be installed as a paymentObserver at Application startup in the viewDidLoad: method.
PaymentTransactionObserver *observer = [[PaymentTransactionObserver alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
In the paymenttransactionobserver i have the paymentQueue:updateTransactions method: (same as describet in Apple's documentation)
(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;
}
}
When i buy a autorenewable product, the product will successfully be purchase.
But it will never be autorenewed. I thought of, that the transaction observer, somehow will get deallocated, but it won't (Otherwhise, i would be notified by the debugger). I also though, i did remove the observer but it will never be removed.
I used the debugger to ensure, that the updateTranscations: method will get called, but nothing. When i buy a test product (in sandbox-mode) with autorenewal time of one week, the method should get called after 3 minutes, but it wont.
What am i doing wrong?
Can anybody help?
Br Nic
If a subscription is autorenewed, the transaction won't pass the paymentQueue:updateTransactions method. The renew just happens on the Store. If you want to test for it you have to either:
Revalidate the receipt on your application server, if you store the receipt there.
Revalidate the receipt on ur iOS client
(http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/VerifyingStoreReceipts/VerifyingStoreReceipts.html#//apple_ref/doc/uid/TP40008267-CH104-SW1)
In order to avoid testing for an autorenew each launch/activation you should store the endDate of the subscription period to test for a renew afterwards.
Also see:
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/RenewableSubscriptions/RenewableSubscriptions.html#//apple_ref/doc/uid/TP40008267-CH4-SW4
However, there seems to be a bug in the sandbox. Subscriptions sometimes get renewed, sometimes not. Hard to test....
Auto-renewals only get posted to your app's purchase queue on app launch or when it cycles back from being backgrounded. Try clicking the home button and then returning to your app.
I got so far: After a reinstall, a user needs to click "buy feature", then he gets scared with the $0.99 question, then has to login and then gets told the feature is already bought and he gets it for free.
I know apple is a religion and users are strong believers, but isn't there a better way? :-) What I want is to check for the feature without actually buying it. Letting the user enter his account info seems to be neccessary, maybe buy a $0.00 feature? or is there a method somewhere that does this?
I'm using MKStoreKit for the whole In-App-Purchase, but any solution would be great.
UPDATE
thanx to darvids0n, your method solved my problem! here's some working code for others trying the same:
- (void)removePreviousPurchases { //just for sandbox testing
[[MKStoreManager sharedManager] removeAllKeychainData];
}
- (void)restorePreviousPurchases { //needs account info to be entered
if([SKPaymentQueue canMakePayments]) {
[[MKStoreManager sharedManager] restorePreviousTransactionsOnComplete:^(void) {
NSLog(#"Restored.");
/* update views, etc. */
}
onError:^(NSError *error) {
NSLog(#"Restore failed: %#", [error localizedDescription]);
/* update views, etc. */
}];
}
else
{
NSLog(#"Parental control enabled");
/* show parental control warning */
}
}
If the $0.99 item is non-consumable, then you should provide a "Restore Purchases" button (or similar) which calls
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
Assuming you've added a transaction observer already, and implemented the protocol including a case to handle a restored transaction (with state SKPaymentTransactionStateRestored) this will work.
Add these two methods :
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue]restoreCompletedTransactions];
This question already has answers here:
Differentiating between initial buy and free "re-buy" in StoreKit/In-App Purchase
(3 answers)
Closed 9 years ago.
Is there a way to find out when a user buys a non-consumable, if he buys it for the first time or has bought it already and gets it again for free?
I checked transactionState, transactionDate of the transaction, but in both cases the data is the same:
transactionState: SKPaymentTransactionStatePurchased (and not SKPaymentTransactionStateRestored in case the user bought it already)
transactionDate: the date at which the transaction was added to the AppStore's payment queue.
You can check Array of transactions which will fill after restoreTransaction method and if Array of transactions is empty it means that user download this upgrade for the first time. In another case you'll check all transactions in Array and compare transaction.payment.productIdentifier with needful product identifire. If it doesn't exist add payment in transaction Array.
For Non-Consumable In-App Purchase I used following code:
#define kInAppPurchaseProUpgradeProductId #"upgradeProductId"
//...
//your payment code for all SKPaymentTransactionStates
//...
//method called when BUY button press
-(void)purchaseProUpgrade{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
//when restore completed delegate method calls
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{
if([[[SKPaymentQueue defaultQueue] transactions] count]==0)
[self addNewPaymentForProductId:kInAppPurchaseProUpgradeProductId];
else
for (SKPaymentTransaction *transaction in [[SKPaymentQueue defaultQueue] transactions]){
if (![transaction.payment.productIdentifier isEqualToString:kInAppPurchaseProUpgradeProductId]){
[self addNewPaymentForProductId:kInAppPurchaseProUpgradeProductId];
break;
}
}
}
-(void)addNewPaymentForProductId:(NSString *)productId{
if([SKPaymentQueue canMakePayments]){
SKPayment *payment = [SKPayment paymentWithProductIdentifier:productId];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
}
the only disadvantage is that every time you call restoreCompletedTransactions, window will pop up asking you to enter the password of the current user. This solution ensures that the buy window does not appear more than 1 time for each upgrade but all the upgrades will restore every time when you'll try to buy one of them.
Not sure but maybe you can check if transaction.originalTransacion exists or if is different.