Bug report + some improvements to in app purchasing module

If you make an in app purchase on Android and using a test account you set the Payment status to ‘Test card, always declines’, you’ll notice that the productPurchaseFinished() method in the InAppPurchases::Listener interface is never called to report the payment failure. So, if a payment failure occurs, your app will be stuck waiting endlessly for the transaction to conclude.

Referencing the code in the develop branch, if you go to line 796 in modules/juce_product_unlocking/native/juce_android_InAppPurchases.cpp, this assignment always fails if the transaction fails:

if (auto* object = responseData.getDynamicObject())

Because there is apparently no response data if the transaction fails.

Unfortunately, due to the way the code in this class is structured, it doesn’t seem to be possible to get the listener without this response data, b/c a pointer to the listener is being stuck in the developerPayload of the response data.

Also, the in app purchasing module isn’t providing callers with the information that they need to verify a transaction on the server side. I made some modifications to support server side transaction validation on iOS and Android. I’d appreciate it if someone could take a look at this patch and consider incorporating it. (It should apply without problems against the code checked into the develop branch.)AddReceiptsAndSignaturesToInAppPurchaseModule.txt (5.3 KB)

Also, it would be nice if on Android the main app activity had a getInstance() method. If someone wants to add their own Java code to customize their apps behavior on Android, you sometimes need a way to get the activity to be able to register receivers. Could someone look at this small patch that just adds a getInstance() method to the JuceAppActivity class?JuceAppActivityGetInstance.txt (1.0 KB)

Thanks for raising the issue, it’s an oversight on our end that there is no callback when a purchase fails. We’ll fix it up as soon as we can.

Sure, we can add signature and receipt information on purchase. Bear in mind though that transaction.transactionReceipt is a deprecated field and instead [[NSBundle mainBundle] appStoreReceiptURL] should be used. We will probably add a function to IAP API such as: String getPurchaseReceipts() that on iOS/Mac will return receipt in base64 and it will use the appStoreReceiptURL method, Android implementation will do nothing. Also, afaics, there is no receipt-data field in dictionary returned by AppStore server (which your patch uses), so on older iOS versions, the receipt returned in transaction.transactionReceipt upon purchase will be the only way to get the receipt (i.e. you need to store the receipt at that time somewhere on your server I suppose), and the receipt will be empty when returning the list of bought products from InAppPurchases::restoreProductsBoughtList().

I am not convinced about getInstance() added to Activity. Is there no other way you can achieve what you need? You could pass the Activity to objects you need, so that they can have a reference, or you could use getContext() from various places. From JUCE side, you can access the Activity in global android.activity variable.

Once again thanks for your bug report. You can find the fix here .

We will have a look at providing receipt and signature separately.

Great, thanks for the link to the patch. I’ll check it out.

You mentioned that you’ll have a look at the receipt and signature separately. I fixed a problem with that today that I should mention. If a user has already purchased a product on Android, then Android’s response code returns the status BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED. This case was being handled on line 150 of modules/juce_product_unlocking/native/juce_android_InAppPurchases.cpp, and it’s currently being treated as a successful payment. In this case though, there’s no signature or receipt available. It seems to make more sense to me to treat it as a failed payment. The status message explains that the user already purchased the item, and what they actually need to do to fix the problem is use the restore purchases function. It occurred to me while working on this that it would be nice if Juce made the status code available in the case of a failed payment. If the status code were being passed through, one could check it and offer to trigger a restore purchases for example.