I did some more tests, releasing my app as a beta through TestFlight, and it turns out that my code above doesn't provide any copy protection at all. Even with added code to verify the "transaction.deviceVerification" manually.
What I did:
Released app as beta via TestFlight
Downloaded and ran Beta app
Copied the app bundle over to another computer
Initially, it didn't run on the other computer. macOS said that the beta had expired or wasn't valid anymore.
Removed the receipt and signing from the app bundle
Re-signed with ad-hoc signing
Now the app runs and shows the AppStore login window on startup. However, pressing cancel in that window simply dismisses it and the app keeps running as if it was a valid purchase.
My Interpretation:
"AppTransaction.shared" will only return a result if the app has a valid receipt. It will not inform the app if there is no valid receipt. Maybe it also returns if there is a receipt and there was just a crypto issue, but that seems like an edge case.
Conclusion
This is where documentation or guidance by apple would be helpful. Previously, without a correct receipt, the user couldn't start the app at all, as it would "exit(173)".
Now, there is no reliable way for the app to tell that the receipt is invalid. All I can think of is to have a global state signalling "receipt was verified" and unless that is true, bock all UI actions the user might want to perform in a purchased app. So, kinda treat the purchase as an in-app purchase required to do anything.
Now the question is:
In which scenarios might my app end up with an invalid/missing receipt? Should it be prepared to recover from that and offer the user to refresh the receipt like it would for in-app purchases?
Sprinkling my code with a bunch of "if !g_hasReceipt { complainToUser() }" in every IBAction also seems like an odd pattern. Is that really the right thing to do?
Also, having to do experiments to find out how the StoreKit is supposed to be used feels like a rather flawed approach. Did I overlook a major part of the documentation or am I just not getting how to find out about system changes like this?!
Post
Replies
Boosts
Views
Activity
Thank you for your replies and sorry if my posts are a bit all over the place...
When I copy the app over to another user account on the same machine, it already behaves differently. In the other user account, it does show a log in window, but if you dismiss it, the app just keeps running. So it seems like the .unverified branch is never reached and instead, "try await AppTransaction.shared" just never returns.
That the app behaves differently on a developer account and on a normal user account on my computer is surprising to me and together with the fact that .unverified is never reached it looks like I don't properly understand how this is supposed to work.
I am feeling really uncomfortable releasing an app if I don't have a solid understanding of how exactly the appstore verification works and that's why it is so frustrating that I cannot find any detailed documentation for my usecase. If all new users pay for an app and then cannot use it, because the verification that is always true on a dev account doesn't actually work, that'd be a bit of an issue :(
I wonder if this is the same issue I encountered with my paid app on macOS, where the app always thinks it is a purchased app, even though no receipt is available. I noticed that running the same macOS app in a different, non developer user account on the mac would make the app correctly detect that it was not purchased.
Maybe this is a Xcode 16 / iOS 18 / macOS 15 bug?!
See my post here:
https://developer.apple.com/forums/thread/769357
Maybe I should add, that I was originally using the exit(173) method and classic, manual receipt validation. However on macOS 15 I started seeing a popup, telling me that "The exit(173) API is no longer available." and asking me to switch to "Transaction.all or AppTransaction.shared to verify in-app purchases"
Upon further reflection, I am wondering if this is just a bug in Xcode / macOS 15?! Because I am actually not using any in-app purchases at the moment. In my last test macOS even got stuck in an infinite loop, redisplaying the same alert every time I pressed "OK". I had to log out of the user account to get rid of the alert...
I also searched for documentation and googled a lot more in the meantime, but could only find other posts and messages from people equally confused about this.
An old WWDC21 video "Meet StoreKit 2" explicitly states:
One final thing to note is that these new JWS objects are for in-app purchase only.
So if you need to validate the app receipt, you should use the existing API and process for that.
Which sounds to me like the manual receipt parsing is still the way to go for paid apps. Or is the "existing API and process" something else? Or is that video from 2021 is just too old and not up to date anymore?
Is there anything documenting this in writing? How can I make sure to be informed about such changes in the future?
I did another test with the app, running it in another user account on the same machine, and there it indeed opened the login window.
So, now I just have to figure out why the app in the dev account thinks it was purchased, but I don't really know how to do that. The purchases used to be stored in a _Receipt file in the app bundle, but I couldn't find that anymore. Did something about this change?
This seems like a bug and you can report it directly to apple at https://feedbackassistant.apple.com
You could also first check if it is still in the latest version of Xcode (4.3), as it might already be fixed.
Thanks for the reply, and for the link to the forum FAQ!
I actually wasn't aware that this forum had a FAQ and at least when I gave it a quick try right now, I wasn't able to find it from the forum's homepage... Probably I am just looking in the wrong place, but thought it might still be usable feedback for the Apple Forum UI/UX people. ;-)
I also didn't find any questions about this issue in the forum (actually used search first), which seems a bit odd given it's a FAQ. Again, maybe I could have tried harder, but maybe there is also a way to improve this in the forum at some point ;D