Biggest problem is currently the Apple Sandbox environment. I was going nuts about not being able to download any products, despite everything configured correctly on the AppstoreConnect side.
I was using RevenueCat with the hope of easier integration into the App and server-side verification yadiyadiya. Didn’t work either. So I implemented a parallel in-app-purchase plugin pay-system where I can easily switch between either RevenueCat or in-app-purchase. I want to know, where the problems I have stem from, so if I use in-app-purchase, then communication goes straight to Apple, whereas using RevenueCat flutter plugin, communication goes mostly to RevenueCat, partly to Apple.
As both didn’t work, the problem needed to be with the communication to Apple. It’s really interesting, that signaling / error propagation is absolutely none here, so it’s really hard to find the real reason for a defect behaviour. In my case, fetching the products simply blocked endlessly. I was using a timeout block around the call, because both plugins don’t support setting any timeout values.
Finally, after restarting my phone (!) everything started to work. Sorry, if this gets a bit longer here …
The real fun on my end is the detection logic for product cross-grades in the Sandbox environment …
So we have this monthly and yearly subscription. Both are placed at the same priority in AppStore Connect, i.e. switching between these is effectivley a cross-grade. In the sandbox environment (not the one from XCode, I mean the AppStore sandbox), monthly subscriptions come in after 4-5 minutes.
In case of purchasing a yearly subscription after the monthly subscription, first a pending purchase arrives and theoretically when the new subscription activates a “purchased” status of this product arrives. For the transition period, I am tagging the newly purchased subscription “pending”. This is also clearly visible to the user with exact dates, etc. so that he/she can easily grasp the current subscription and when the new purchase gets active.
But the sandbox sends a few seconds later already a “purchased” event and even doesn’t wait until the current subscription ends. This wrongly activates the yearly subscription and tags the monthly purchase as available to be cross-graded to.
My current work-around is to ignore any “purchased” event from the AppStore for pending products up-to 1 minute after a purchase, if a currently active subscription exists. This heuristics seems to work very well, but now I am diving into the “restore purchases terrain”, and I am absolutely not sure, how this above scenario can be detected here for the sandbox.
What I find really interesting is that most code I find related to in-app-purchase is absolute demo beginners-code that doesn’t come close to production-grade solutions. What if the network is not available ? What if suddenly the network becomes available ? What about the above concrete (and quite common) scenario ?
I ended up with an architecture, where I clearly separate the service, have an own PayStore, PayXXXRepositories, PayScreen with PayScreenCards for each product, etc. where online-offline scenarios are built-in and where the single source of truth is the PayStore and never the service. My PayService is abstracted so I can easily switch between different service providers.
What holds me off from RevenueCat, besided being another point of failure between my App and Apple is the vendor lock-in. I’d use it only for server-side receipt verification, so I am really thinking about rather self-hosting a game server like Nakama, which already has built-in AppStore/PlayStore, etc. receipt validation and where you can do a lot more yourself (if you want to).
Coming back to the IAP integration: choosing the underlying service is IMHO an important but minor part of the implementation. Most business logic stems from the complexity of IAP requirements of the AppStore itself and the production-grade complexities related to make the purchase flows reliable for every typical scenario.