gdx-pay icon indicating copy to clipboard operation
gdx-pay copied to clipboard

iOS subscription expiry is not handled

Open keesvandieren opened this issue 3 years ago • 6 comments

Please ensure you have given all the following requested information in your report.

Issue details, reproduction steps/code

The iOS Robovm implementation is not handling subscription expiry.

If you have ordered a subscription once, you currently have access for life.

Subscription receipts should be handled.

Version of gdx-pay and/or relevant dependencies

1.3.0

Stacktrace

N/A

Please select the affected platforms and payment service implementation

  • [ X] apple robovm

keesvandieren avatar Sep 26 '20 17:09 keesvandieren

Parsing the Receipt seems to be fairly complicated. A Swift example is shown here:

https://github.com/andrewcbancroft/SwiftyLocalReceiptValidator/blob/master/ReceiptValidator.swift

Has anybody implemented this in Kotlin or Java?

keesvandieren avatar Sep 28 '20 20:09 keesvandieren

Related to: #167.

Approach I'm planning to follow:

  • extend class Transaction with field subscriptionValidUntil. This will be filled with transaction startDate incremented with Product subscription incremented with Billing Grace Period (6 days for weekly, 16 days for longer subscriptions).
  • use this field, to validate transactions in the Java logic.
  • see if we can also populate this field in Google Play implementation.

Additionally, server validation can be used, but I currently have not enough time to implement that properly. Maybe in 2 or 3 months I have time

keesvandieren avatar Oct 02 '20 19:10 keesvandieren

After some more thinking, I am proposing the following changes to support basic end-date calculation in Google Play and App Store, without receipt verification:

  • Rename class FreeTrialPeriod to SubscriptionPeriod
  • Add Information.subscriptionPeriod
  • Add field: Transaction.information, which is a reference to the related Gdx Information instance
  • In Transaction, add a method: @Nullable public Date calculateMaximumUsageDate(int gracePeriodInDays). This uses Transaction.purchaseTime, adds the subscriptionPeriod, then adds the gracePeriodInDays, and returns that Date.

Apps have to maintain billing grace periods themself. On iOS App Store, the period is dictated by Apple; however, enabling or disabling billing grace period is to be chosen by the developer. On Google Play, the developer can choose whether to enable billing grace period, and, if enabled, can choose from a number of predefined values. Billing grace period is not exposed through payment apis (on Google Play and App Store).

External links:

Eventually, we can add the billing grace period to Offer, but that would only work for Google Play, as for App Store it is decided by Apple. So that is why I prefer to keep it out.

@MrStahlfelge what do you think of this? (or any other reader interested in subscriptions)

keesvandieren avatar Oct 03 '20 09:10 keesvandieren

I am not really into subscriptions (don't using them), but what you suggest seems viable. As you are using subscriptions, best is if you try in your fork if it works out for you. And if it does, let's add it to gdx-pay!

MrStahlfelge avatar Oct 03 '20 11:10 MrStahlfelge

@keesvandieren :

  • have you fixed this bug?
  • is this (not expiring subscriptions) apply to Non-renewing subscriptions also?
  • is subscriptions expire properly on Google Play?

aberkowski avatar Dec 29 '23 08:12 aberkowski

@aberkowski it is not solved in gdx-pay yet. Apple returns all the historical subscriptions as of now. Receipt validation in Java/Kotlin seems to be hard to implement, didn't try it as I have no time for it.

How I handle it now:

  • find the newest Transaction (in handlePurchase or handleRestore)
  • calculate the subscription end date of the transaction
val isPurchased = lastPurchaseValidUntilMillis > System.currentTimeMillis()

This doesn't take into account refunds, and also when user changes the clock (back in time) it can use the app with an old subscription.

One thing you could implement, is a backend which retrieves the Apple events and then call an API to your own backend to see whether the subscription hasn't been refunded. See https://developer.apple.com/help/app-store-connect/configure-in-app-purchase-settings/enter-server-urls-for-app-store-server-notifications/ for more info.

Also, https://github.com/libgdx/gdx-pay/tree/master/gdx-pay-server is available as a starting point for server-side receipt validation but I've never used it; seems @noblemaster has some understanding and might actually use it

keesvandieren avatar Dec 29 '23 18:12 keesvandieren