InAppBillingPlugin: Not possible to purchase the same renewable subscription again after it has once expired (iOS)
Hello,
I am not sure if its a bug or if I am doing something wrong. I have just updated from version 4.0.2 to version 5.4.0 (I have also tested version 6.4.0 with the same result). Since version 5.4.0 I call the FinalizePurchaseAsync-function after the validation of the iOS renewable subscription purchase. That is all, that I have changed in my code. Since version 5.4.0 I cannot purchase the renewable subscription with the same product_id after it was once expired with the sandbox tester account. In the old version 4.0.2 it works as expected. Does anyone have any idea what went wrong here?
With regards DiebBlue
Bug Information
Version Number of Plugin: 5.4.0 (same behavior with 6.4.0) Device Tested On: iPhone SE (2016) Simulator Tested On: no Version of VS: 2019 Version 16.11.18 Version of Xamarin: 16.11.000.197 Versions of other things you are using: Xamarin Forms 5.0.0.2337 Xamarin Essentials 1.7.3 Xamarin.CommunityToolkit 2.0.1 Xamarin.iOS and Xamarin.Mac SDK 15.2.0.17
Steps to reproduce the Behavior
- Delete purchase history for Sandbox test account in Apples App Store Connect
- Start the App and purchase a 1 month renewal subscription
- Close the App
- Start and Close the App - The subscription will renew a few times correctly until it has been expired after a few loops
- Start the App and purchase a 1 month renewal subscription again after it has been expired
Expected Behavior
- the subscription dialog window raises and the user can purchase again the 1 month subscription as before
Actual Behavior
- no subscription dialog window raises
- the billing.PurchaseAsync functions just returns the already expired old purchase
Code snippet
//You must connect
bool connected = await billing.ConnectAsync();
if (!connected)
{
//Couldn't connect
return null;
}
//make purchase
purchase = await billing.PurchaseAsync(product.ProductInfo.ProductId, product.ProductType);
//possibility that a null came through.
if (!CheckPurchase(product.ProductType, purchase))
{
//did not purchase
}
else
{
if(Device.RuntimePlatform == Device.Android)
{
// Android
await ValidatePurchase_Android(purchase, billing, product, false);
}
else
{
// iOS
await ValidatePurchase_iOS(purchase, billing, product, false);
}
return purchase;
}
Screenshots
Debug breakpoint:
Contents of the purchase at the breakpoint:

About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 2
- Comments: 21 (7 by maintainers)
Awesome @jamesmontemagno Hope to get this in and tested shortly! Thanks for your quick efforts, I was seriously concerned a few days ago…
@adam-russell @yonkahlon @jamesmontemagno thanks for your input on this thread!
Setting that flag, did help, thanks for pointing it out, I completely missed adding that.
When calling the PurchaseAsync(); method, the purchase dialogue does not always show on a new subscription. Not sure why but if it doesn’t and simply returns the purchase object, all fails. Will keep testing.
When the app starts, I do a GetPurchasesAsync() I loop through all purchases returned (100s in the test env., shouldn’t be many on real world), I validate the purchaseToken on each. I noticed that the IsAcknowledged flag is always false on the most current valid purchase (it was “acknowledged” at time of purchasing). Not sure if we should have to call FinalizePurchaseAsync() method on current purchase/subscription every time, maybe there’s a reason for it still being false.
MUCH BETTER! I’m a little less stressed and hopeful this can be deployed soon.
I will pay attention to updates and will add the 6.5.0 release version when it’s ready.
Now to test Android…
Thanks to ALL!
@blmiles
You should be able to use the new beta that James put out if you include prerelease versions – it’s 6.5.0-beta. Then, set the new flag so it works like 4.0.2 where you initialize the plugin in your AppDelegate:
At least for me, this makes it work nicely like 4.0.2 on initial tests just like @yonkahlon above.
@jamesmontemagno Great, thanks for the clarifications!
So, with the
FinishAllTransactionsflag, everything seems to work for me as I mentioned above.Trying to implement it without that, I’m still having issues. Because of the version of Xamarin.iOS I’m using in my build, I’m having issues running the debugger on a physical device, so I’m partially guessing below (so take with a grain of salt or whatever):
One of the problems I missed before (sorry about that) that @blmiles is referencing above is that
FinalizePurchaseAsyncalways returns false when I try it with a just completed purchase when the transaction isn’t the original. I think the problem is that it’s trying to useRestoreAsyncthen loop through the transactions to finish the right one. (Thanks for the loop in there, just in case!) The problem looks likeRestoreAsyncadds originals from the current queue and restores completed transactions (https://developer.apple.com/documentation/storekit/skpaymentqueue/1506123-restorecompletedtransactions) so the one you’re trying to complete isn’t always there. I think it’ll be on SKPaymentQueue.DefaultQueue.Transactions while there’s an observer attached (https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions)? I also haveOnPurchaseCompleteimplemented in my version of the code without theFinishAllTransactionsflag, and it gets partially sorted out when I kill and restart the app (maybe because I’m also verifying the latest receipt with Apple?) , but without the debugger attached, I’m a little confused about what’s happening.@jamesmontemagno thanks so much! Just tested with the flag and it works for me:
InAppBillingImplementation.FinishAllTransactions = true;I tried without the flag, and follow this flow:
This didn’t work for me with 6.5.0, but maybe I’m doing something wrong. I “Restore Purchase” on start up to check that the subscription is still valid.
Just for clarity, shouldn’t you call FinalizePurchaseAsync once in step 4 since it loops through all transactions for you?
Also, just to reiterate what @adam-russell said: thanks so much for this library, it’s saved so much time having to figure out to implement Android and iOS billing!
Thanks for all the research and write-up on this. I guess that all makes sense to me on the renewals for sure.
I did make a change in this latest beta that I just pushed out.
When you call FinalizePurchaseAsync… it will find all with the transactionIdentifier:
On a real flow I would: 1.) User purchases sub 2.) Finalize 3.) When you need to “renew” or you have “restored” call “GetPurchasesAsync” 4.) Loop through and finalize ALL purchases.
That should handle it to be honest. You could also do step 3/4 when the user tries to buy the purchase again.