I make a fresh install of my App on my iOS device. (I had deleted it, signed off from the GC account).
After the App launch, 'paymentQueue: updatedTransactions:' gets called. It has one transaction with transactionState' == SKPaymentTransactionStatePurchased. (It's the only iAP my App has, in fact).
And then the GC Sandbox Login ViewController is presented.
Why does the iAP object get the Bought State, if I haven't logged in neither with an iTunes account or GC account?
I am not login on Game Center, that's for sure. But I'm not so sure about being being logged in with an iTunes Store Sandbox account. (Does that even exist?) If so, how do I log off?
I want to be able to test my App's 'Restore' button, but as the function already gets called, and the item is shown as bought, I can't... Why does even the function get called when I add the observer to the SKPaymentQueue? To check unfinished transactions? But it shouldn't do so as I'm not supposed to be logged into an iTunes Account...
Please, tell me any ideas you have, this is making me go crazy.
It sounds like you might be failing to call [SKPaymentQueue finishTransaction:] after your transaction is processed - the IAP system will keep attempting to deliver the transaction on every startup until you do that. So just add that call when you're done processing the transaction and you should be all set.
Related
I got most of IAP purchase working, using user defaults etc
But supposing a user buys an app, then erases or updates to a new phone.
Then reinstalls the app.
How do I detect that the user has previously purchased the app?
That is what restore functionality is for. See the Restore section of:
https://developer.apple.com/documentation/storekit/in-app_purchase/offering_completing_and_restoring_in-app_purchases
There are three ways:
If you store some purchase indicator in the app keychain, it will be present when the app is re-installed. If you are doing subscriptions also include an expiration date you can check if a subscription is possibly out of date.
If you send the app receipt up to Apple, it will always give you back the most current purchase data - which includes any prior purchases. So on first launch, always send the receipt up to Apple to see if you might want to automatically restore a purchase made previously. Note that sometimes an application receipt will not be present immediately after install (although it should always be there), which is why you also need to allow for option 3.
There is also a SKPaymentQueue’s restoreCompletedTransactions() call you can make to basically replay previous purchase transactions. This is something you should wire to a button somewhere in your UI - Apple requires that. It's not something you want to automatically call as it can prompt for the user to enter their iTunes account password, so you should not rely on this method to restore purchases automatically, but it is good to have in place as a backup for the user.
On a fresh app install, when I try to re-purchase an item to re-download it my app sends a purchase request to Store Kit.
Then I get SKPaymentTransactionStatePurchasing, and Store Kit prompts the user to confirm the purchase.
Then Store Kit responds with this message on iOS 7: "You've already purchased this in-App Purchase but it hasn't been downloaded.".
Then, Store Kit fails with SKPaymentTransactionStateFailed and error is
Previously StoreKit would just let the user re-download the content instead. But now it fails with error code 2 "Cannot connect to iTunes Store". If I do a complete restore then Store Kit allows to download content, but this particular item still fails. Also note the test device has WiFi and stable internet connection and StoreKit was able to resolve that the item is purchased. So this error is bogus.
Is this a new change in iOS 7? How can I let the user re-download a single item without forcing to re-download all?
According to this Technical Note, the problem occurs if there is a purchase transaction that was not finished. I tripple-checked that I call finish transaction and it happens after I get the error. So the transaction does get closed.
You have some unfinished transactions in the payment queue. To finish them check the paymentQueue.transactions array right after adding your observer with addTransactionObserver: method. If it contains some transactions handle them the way you do in your paymentQueue:updatedTransactions: method.
If you don't, paymentQueue:updatedTransactions: is not called for them because they were already in the queue when you have registered the observer.
By the way, make sure you call finishTransaction: for all of your transactions even if they have failed. Otherwise they will stay in the payment queue and cause this issue.
Same problem here.. we launched a new update for our app after iOS 7 release. We have many users now complaining that they are either unable to restore previous purchases. Other users are complaining that their purchase is successful, however the IAP package does not unlock with no error message.. we are a bit baffled here.
I had same problem ! After checking for hours! I just restarted the I pad (iOS7), and now again its working.
I fixed this by signing out the test account from the App Store via Settings app -> App Store -> Sign Out. Then I removed the test account from iTunes Connect, then I rebooted the device.
I was in a similar situation; resolved by restarting my iPhone. Specifically, I had non-consumable purchase hosted on Apple's Server. Installed app to iphone5, made purchase. Deleted App, reinstalled in another build, and upon restore, ITunesStore server didn't respond with my hosted-contact productIdentifier. Purchasing the item again produces "already purchased" error, without an option to download. I found this site, http://support.nimblebit.com/customer/portal/articles/672080-problem-making-in-app-purchase-ios-os-x; restarted my device, and restore now recognizes the previous purchase.
I am updating my iOS app with the Apple suggested transaction VerificationController code to verify in-app purchases due to the recent "hack" published that allowed people to purchase in-ap purchases without paying due to spoofed receipts from spoofed Apple servers.
I have everything integrated, and am now testing. I have run the app several times and the verification stuff has run several times.
I want to test everything about purchasing including starting with a fresh brand new app and AppleID. So I deleted the app completely off my testing device. I created a brand new "test user" AppleID in iTunes Connect. I went to the Settings app on my test device, went to Store, and changed the default AppleID for the device to this newly created AppleID.
I re-run the app from Xcode with the debugger, which re-installs the App onto the testing device and runs it fresh.
The problem is that almost immediately on launch, the testing device puts up the AppleID password Alert-type view and asks for the password for the old AppleID that I originally used to test everything including the original in-app purchase and the verification for it. It does not ask for the password for the new device AppleID as set in the Settings app under store.
When I run it as a new app, the verification code does not run and no code from any of my routines that do anything with the Apple StoreKit stuff is run except for a solitary
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
(observer is my delegate object for the StoreKit stuff and is created but no routines in it run except init and init does nothing except set a static variable for itself to create a singleton type class)
For checking purposes, I also added in
NSLog(#"in App Delegate, payment queue transactions are %#", [[SKPaymentQueue defaultQueue] transactions]);
which shows no old transactions hanging around.
I have no clue on why it has started to ask for my AppleID of the original test user when the App is newly installed, the AppleID for the store for the device is different, and I can identify no code being run that accesses the StoreKit (except as mentioned above).
ANy insight into this would be appreciated.
ETA: Touching CANCEL in the password dialog does not cause any extra code to run in the app and does not prevent the app from running.
If I then go into the in-app purchase screen (where the app queries the store for available in-app purchases), it asks for the password again in the same way, on the OLD AppleID. I can cancel and nothing seems to happen. If I actually touch "restore purchases" button in my app, it then asks for the password on the new AppleID as set up in the Store settings. I have not gone past this point as I want to understand what is going on and don't want to mess things up by completing/attempting the restore or purchase under the new AppleID.
Thanks
I had exactly the same problem and struggled for hours to solve it.
As previously mentioned, StoreKit works system-wide and the system keeps asking for login for any transaction for which
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
has not been called. I was using RMStore, looked at the source code I found this suspect lines:
if (error.code != RMStoreErrorCodeUnableToCompleteVerification) {
// If we were unable to complete the verification we want StoreKit to keep reminding us of the transaction
[queue finishTransaction:transaction];
}
This is probably what happened:
I first purchased the product with my test Apple ID
I deleted the product for test purposes
I tried to purchase it again. At this point I entered wrong credential and RMStore did not call [[SKPaymentQueue defaultQueue] finishTransaction:.
The app started asking for login at every startup.
I tried to login with a different test account but still the same alert asking to login with the old Apple ID. I also deleted the Apple ID on iTunes Connect but this did not help.
Finally I managed to solve by Restoring (not Purchasing) the product with the same account.
I don't know if there is another way to solve this issue (i.e. to reset the StoreKit queue). Anyway always remember to call finishTransaction in your code.
StoreKit.framework works system-wide, so the Apple ID that gets displayed doesn't have anything to do with your app, and reinstalling the app will not have any influence.
However, you can log out from the iTunes Store / App Store:
Tap Settings → iTunes & App Stores
Tap the Apple ID signed in (button at the top)
Tap "Sign Out"
When you then use your app again, StoreKit should prompt for both email and password.
Steps to Fix:
Starting at the App Store, pull up the multi-task bar and tap the Settings app.
Scroll down and tap 'General'.
Scroll down and tap 'Date & Time'.
If the 'Set Automatically' switch is ON, turn it OFF.
Tap 'Set Date & Time'.
Tap the field that says "[day of the week], [month][day], 2013".
Adjust the year wheel to say "2012" and back out of the Date & Time section.
Multi-task back to the App Store and attempt to download the app again.
Tap 'Cancel' on the "Cannot connect…" modal.
Return to the native Settings > General > Date & Time > Set Date & Time.
Go back to the month, date, year wheels and change the year to "2030".
Back out to the home screen and launch the App Store.
A "Cannot connect to the Store" modal should appear right away.
Tap 'OK' and then multi-task back to Settings.
Return all the way back to the month, date, year wheels again and change the year back to "2013.
Multi-task back to the App Store and attempt to download the app again.
My app is using in-app purchases, and most of my users can purchase just fine without any problems. For these folks, my app downloads the content after the purchase succeeds and they are happy.
However, for a growing number of my users, once they complete a successful in-app purchase they are being asked for their App Store password every time the app starts up after that. I believe this is happening on the call to:
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
which I am calling on startup in accordance with step 6 in Apple's in-app purchase guide:
archived guide: https://web.archive.org/web/20130515222703/https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/AddingaStoretoYourApplication/AddingaStoretoYourApplication.html
actual guide: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/DeliverProduct.html#//apple_ref/doc/uid/TP40008267-CH5-SW4
My guess is that, for some reason, Apple's in-app purchase servers aren't registering that the transaction finished successfully - even though I call
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
when the transaction is completed and my content has been successfully downloaded.
2 questions:
Is anyone else seeing this?
Does anyone have a suggested fix?
BOUNTY EDIT:
Its a transaction which was made with a different Apple-ID. Thats why it cannot be finished unless you type in the right credentials into the dialog. The Question should be either:
How can I prevent such dead transactions (transaction has not been finished, user has no network, meanwhile changes App-ID)?
How can you prune the SkPaymentQueue?
I had the same problem.make sure that you call
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
for all three states of the transactions: SKPaymentTransactionStatePurchased, SKPaymentTransactionStateRestored, SKPaymentTransactionStateFailed.
I had the same problem of having the login prompt coming up at the call:
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
It would also come up every now and then even when I wasn't using my app (on the home screen or in other apps), which was really annoying. Looking around, there seem to be so many suggested answers to this issue but I finally found a solution from a combination of what I've gathered.
Note: Before step 1, I had removed the test sandbox account in iTunes Connect. I'm not sure if that would affect the solution.
To solve the problem this is what I did:
Run your app from Xcode.
Wait for the prompt to come up. Type in the password for the account it wants and tap OK.
Press the Home button on the device.
Kill the app from Xcode.
Delete the app from the device.
Log out of iTunes & App Store in the Settings app.
Turn off the device and then turn it back on.
Purchase something from the App Store. When it prompts you, log in with a production Apple ID account. (I'm assuming you should be able to just log in with a production account in iTunes & App Store under the Settings app but this is how I did it).
Go back to Xcode and run your app again. (This should be a new install, as you deleted the app before.)
Wait for the login prompt to come up.
Tap Cancel. A dialog saying "Sign In Required. Tap Continue and sign in to check for downloads. [Environment: Sandbox]" should come up. This was a key difference from before. I never had this dialog come up when I pressed Cancel when it was asking me for the password.
Tap Continue.
Enter the password for the account.
That's it. From then on the login prompt stopped coming up whenever I ran my app and also stopped coming up at random times.
Hope this helps!
DO NOT DELETE THE ANSWER HERE. It was this particular Stackoverflow question that misled me and messed me up for days.
I'm putting this here because there are a lot of really bad answers that provide WRONG information on how to resolve the problem.
DO NOT:
Delete the sandbox test user. This makes it impossible to resolve the problem and you will have to contact Apple developer support to resolve manually.
If you delete the sandbox test user, when you are subsequently repeatedly prompted to log in as that user and complete the transaction, you can't, hence the name Endless Loop problem. Nor will you be able to add the deleted test user again; the developer portal says the user id has already been used.
Delete the App or re-install iOS or any other such nonsense. It has no effect, doesn't solve the problem and wastes a lot of time.
DO:
Call Finish for ALL transactions.
If one is interrupted for some reason, simply complete on a subsequent run of the App. The app will be repeatedly sent the payment queue notice until you call finish on it:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
That's it, Finish all transactions! Else you will be sent to the hell of the Endless Loop of sign in requests every single time your App launches on that device.
There is a problem called the "endless loop". It was big issue back in the early days of auto renewables when, for about a week, the servers did not limit renewals to 5. A device that gets a transaction and doesn't call finishTransaction will get that transaction delivered to the device about once a week until that particular test user logs in and calls finishTransaction. If you switch to airplane mode you can 'clear' those transactions for another week - but they come back.
I suspect that this is a correct behaviour. When you set a delegate SKPaymentQueue try to check if there are some transactions to finalize. There may be no not finished transactions but the fact of checking requires to login in iTunes. And I think you can do nothing with it.
It generally has some sense, but it is pretty annoying for users who have set up asking for a password on each transaction (some child protection for instance). So the only way to struggle with it is to set delegate explicitly when you are about to request iTunes. For example you can add some button like "Restore my purchases". Not very beautiful but definitely less annoying.
If anyone is seeing this and is using GoogleMobileAds.framework then you may also need to call
[GADMobileAds disableAutomatedInAppPurchaseReporting];
in -application:didFinishLaunchingWithOptions:
Here's how I can consistently reproduce and resolve this issue:
iOS 8.4, development build of the app.
Use a test(sandbox) Apple Account.
Restore purchased transactions.
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions]
Immediately close the app.
Log out of test account (through settings)
Launch app
Now every time I launch the app it will pop up the "login" form. It doesn't matter if I enter the password or not. It doesn't matter if I make purchases or restore the purchases. It doesn't matter if I delete and re-install the app. Every time I launch the app, the iTune login is presented.
Fix: Hard reboot the device.
Here's what I don't know. Will this happen in a production environment (published app and actual apple login)? I hope not.
I had the same problem when testing IAP.
I tested with 3 test account. The app kept asking for password for both accounts. Even if I didn't touch any purchase/restore button or addTransactionObserver.
I think this is because some previous transaction has not finished correctly, but [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; can't help at all.
So here's what I did to solve this problem:
input the password for each account no matter how many times app
store ask for--I input 6 times for 3 account--until it never ask
again.
go to the setting and sign out the apple ID.
quit the app normally--don't terminate it in Xcode. then kill the process in task list.
remove the app from device (simply remove the app won't work it'll still ask for password )
reboot the device
Run the app again from Xcode/in your case reinstall the app from app store.
inspired by Expected sequence when using iTunes test user
Deleting and re-installing the app will remove any old transactions associated with another itunes account. If you are still seeing transactions posted to the notification queue, then you likely had some branch in your logic that did not call finishTransaction.
You need to to call finishTransaction on all transactions that are posted to paymentQueue:updatedTransactions:, even ones with SKPaymentTransactionStateFailed.
bugs related to in app purchases fixed in the iOS update 5.1.1
http://support.apple.com/kb/DL1521
Check for the following as I had it in my viewDidLoad method. I had an app rejected when apple wanted me to go from non-consumable to consumable, however I left the following line in my viewDidLoad method: (I thought the password prompt was a issue with swapping from the test user to normal user back to the test user)
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
Once removed the request for password stopped.
And I have left: [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
in and the app has now been approved just fine.
You know, I resolved this problem by making a modify in my updatedTransactions. I didn't add the [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; for all situations.
I resolved it by deleting the iTunes Connect test user account related with this issue. App actually asked once again to sign in, but then it disappeared (I've also deleted app, restarted iPhone and logged in as a normal non-sandbox user, made a real restore in other non-sandbox app and then launched the app)
You said:
However, for a growing number of my users, once they complete a successful in-app purchase they are being asked for their App Store password every time the app starts up after that. I believe this is happening on the call to:
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
Note that the docs for add(_ observer: / addTransationObserver: state that the call may cause the user to authenticate with the App Store, so this appears to be expected behavior.
// Observers are not retained. The transactions array will only be synchronized with the server while the queue has observers. This may require that the user authenticate.
open func add(_ observer: SKPaymentTransactionObserver)
open func remove(_ observer: SKPaymentTransactionObserver)
So in addition to other answers here, it may be that you should stop calling add observer until you are ready to potentially display an App Store login prompt.
I have created a test app that has in app purchasing. I am able to connect to the store and verify my product ID's. I then use my test user account to purchase a product. And guess what... it works... the first time. If I try to use the test user account to buy another product (the same product or a different one) then I get a pop up that says
"An unknown error has occurred"
with a "Cancel" and "Retry" option. If I retry then I get the same error. After hitting cancel I get the error:
Error Domain=SKErrorDomain Code=0 UserInfo=0x161180 "Cannot connect to iTunes Store"
Any ideas?
Update:
I have found a work around. For some reason apple does like Canadian test users. I switched to using US test users and everything worked.
I also found that after an OS upgrade I needed to delete the app off my phone and do a clean build to get things working again.
So the issue here doesn't have anything to do with wether or not your iTunes connect user is in Canada or the US. It's kind of a strange bug on Apple's side.
What happens is if you sign-out of an iTunes connect user in Settings.app and then sign back in with that user in Settings.app that test user is somehow poisoned and is broken permanently. You will no longer be able to use that account for testing.
To avoid this sign-out in Settings.app, but don't sign back in until you are inside your application and it prompts you during purchase.
I am having this EXACT same problem. I have some more details so let's get to the bottom of this!
When I create a new tester account, the first purchase always works. The second time gives me an alert that says "You've already purchased this but it hasn't been downloaded..." on the device. Every time after that I get an "Unknown error has occurred" message.
My NSLogs gives some more info. The first payment is normal. But it gets weird for any payment attempt after that. After I send the payment request and make myself a queue observer, I see TWO transactions in the purchasing state... weird, why two? After I get the error and tap cancel, I get two transactions in the failed state sent back. Why would I get two responses when I only sent one payment request?
I am using a consumable product type that the user should be able to purchase over and over again.
This worked perfectly for me the last month of testing. It only started acting like this in the past week. The two transactions seems to be from Apple's side, because I can't see any way my code might be causing it. Perhaps Apple made some changes to their purchasing system and haven't worked out the kinks yet.
In my case, I was logged in with another sanbox tester account(of different developer account I mean) in Settings -> iTunes & App Store -> Sandbox Account.
I simply logged out and then tried again to perform the In-App Purchase. And so it asked me to login with another sandbox account. I did so and it worked as it should!