Skip to content

Commit

Permalink
#229 best-effort guessing of subscription end date
Browse files Browse the repository at this point in the history
  • Loading branch information
keesvandieren committed Oct 9, 2020
1 parent 8b10f6d commit 46b2798
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.badlogic.gdx.pay.android.googlebilling;

import com.badlogic.gdx.pay.FreeTrialPeriod;
import com.badlogic.gdx.pay.FreeTrialPeriod.PeriodUnit;
import com.badlogic.gdx.pay.SubscriptionPeriod;
import com.badlogic.gdx.pay.SubscriptionPeriod.PeriodUnit;

import javax.annotation.Nonnull;

Expand All @@ -14,10 +14,10 @@ class Iso8601DurationStringToFreeTrialPeriodConverter {
* the spec</a>
*/
@Nonnull
public static FreeTrialPeriod convertToFreeTrialPeriod(@Nonnull String iso8601Duration) {
public static SubscriptionPeriod convertToFreeTrialPeriod(@Nonnull String iso8601Duration) {
final int numberOfUnits = Integer.parseInt(iso8601Duration.substring(1, iso8601Duration.length() -1 ));
final PeriodUnit unit = PeriodUnit.parse(iso8601Duration.substring(iso8601Duration.length() - 1).charAt(0));

return new FreeTrialPeriod(numberOfUnits, unit);
return new SubscriptionPeriod(numberOfUnits, unit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ private Information convertSkuDetailsToInformation(SkuDetails skuDetails) {
String priceString = skuDetails.getPrice();
return Information.newBuilder()
.localName(skuDetails.getTitle())
.freeTrialPeriod(convertToFreeTrialPeriod(skuDetails.getFreeTrialPeriod()))
.freeTrialPeriod(convertToSubscriptionPeriod(skuDetails.getFreeTrialPeriod()))
.subscriptionPeriod(convertToSubscriptionPeriod(skuDetails.getSubscriptionPeriod()))
.localDescription(skuDetails.getDescription())
.localPricing(priceString)
.priceCurrencyCode(skuDetails.getPriceCurrencyCode())
Expand All @@ -183,7 +184,7 @@ private Information convertSkuDetailsToInformation(SkuDetails skuDetails) {
* @param iso8601Duration in ISO 8601 format.
*/
@Nullable
private FreeTrialPeriod convertToFreeTrialPeriod(@Nullable String iso8601Duration) {
private SubscriptionPeriod convertToSubscriptionPeriod(@Nullable String iso8601Duration) {
if (iso8601Duration == null || iso8601Duration.isEmpty()) {
return null;
}
Expand Down Expand Up @@ -293,6 +294,7 @@ private void handlePurchase(List<Purchase> purchases, boolean fromRestore) {
transaction.setStoreName(PurchaseManagerConfig.STORE_NAME_ANDROID_GOOGLE);
transaction.setPurchaseTime(new Date(purchase.getPurchaseTime()));
transaction.setPurchaseText("Purchased: " + purchase.getSku());
transaction.setInformation(getInformation(purchase.getSku()));
transaction.setReversalTime(null);
transaction.setReversalText(null);
transaction.setTransactionData(purchase.getOriginalJson());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package com.badlogic.gdx.pay.android.googlebilling;

import com.badlogic.gdx.pay.FreeTrialPeriod;
import com.badlogic.gdx.pay.FreeTrialPeriod.PeriodUnit;
import com.badlogic.gdx.pay.SubscriptionPeriod;
import com.badlogic.gdx.pay.SubscriptionPeriod.PeriodUnit;
import org.junit.Test;

import static org.junit.Assert.*;

public class Iso8601DurationStringToFreeTrialPeriodConverterTest {
public class Iso8601DurationStringToSubscriptionPeriodConverterTest {

@Test
public void convertsStringWithFewDays() {

final FreeTrialPeriod period = Iso8601DurationStringToFreeTrialPeriodConverter.convertToFreeTrialPeriod("P3D");
final SubscriptionPeriod period = Iso8601DurationStringToFreeTrialPeriodConverter.convertToFreeTrialPeriod("P3D");

assertEquals(3, period.getNumberOfUnits());
assertEquals(PeriodUnit.DAY, period.getUnit());
Expand All @@ -20,7 +20,7 @@ public void convertsStringWithFewDays() {
@Test
public void convertsStringWithMoreThenTenDays() {

final FreeTrialPeriod period = Iso8601DurationStringToFreeTrialPeriodConverter.convertToFreeTrialPeriod("P14D");
final SubscriptionPeriod period = Iso8601DurationStringToFreeTrialPeriodConverter.convertToFreeTrialPeriod("P14D");

assertEquals(14, period.getNumberOfUnits());
assertEquals(PeriodUnit.DAY, period.getUnit());
Expand All @@ -29,7 +29,7 @@ public void convertsStringWithMoreThenTenDays() {
@Test
public void convertsStringWitSixMonths() {

final FreeTrialPeriod period = Iso8601DurationStringToFreeTrialPeriodConverter.convertToFreeTrialPeriod("P6M");
final SubscriptionPeriod period = Iso8601DurationStringToFreeTrialPeriodConverter.convertToFreeTrialPeriod("P6M");

assertEquals(6, period.getNumberOfUnits());
assertEquals(PeriodUnit.MONTH, period.getUnit());
Expand All @@ -38,7 +38,7 @@ public void convertsStringWitSixMonths() {
@Test
public void convertsStringWithOneYear() {

final FreeTrialPeriod period = Iso8601DurationStringToFreeTrialPeriodConverter.convertToFreeTrialPeriod("P1Y");
final SubscriptionPeriod period = Iso8601DurationStringToFreeTrialPeriodConverter.convertToFreeTrialPeriod("P1Y");

assertEquals(1, period.getNumberOfUnits());
assertEquals(PeriodUnit.YEAR, period.getUnit());
Expand Down
47 changes: 45 additions & 2 deletions gdx-pay-iosrobovm-apple/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,48 @@ Next to other ways, I find the easiest way to test the IAP the following:
* your build installed from TestFlight will have working IAPs that are not charged to the users


(1) In order for you to use your actual IAP in your app you also have to fill some information regarding your App Store Connect account. Normally you will see a warning if something is missing, but sometimes when your app is marked as distributed for free, the warnings won't show. In case of IAP, make sure that you have filled required information in following section:
My Apps -> Agreements, Tax, and Banking -> Paid Apps. It's status should be "Active". As stated there: "The Paid Apps agreement alllows your organization to sell apps on the App Store or **offer in-app purchases.**"
(1) In order for you to use your actual IAP in your app you also have to fill some information regarding your App Store
Connect account. Normally you will see a warning if something is missing, but sometimes when your app is marked as
distributed for free, the warnings won't show. In case of IAP, make sure that you have filled required information in
following section: My Apps -> Agreements, Tax, and Banking -> Paid Apps. It's status should be "Active". As stated
there: "The Paid Apps agreement alllows your organization to sell apps on the App Store or **offer in-app purchases.**"


## Subscriptions

To verify if the user has a valid subscription we recommend server-side validation.

If you do not want to user server-side validation, it can be done by parsing receipt in the App. GdxPay has not
implemented that. Pull requests are welcome :).

It is still possible to find out if user has a valid subscription.

iOS keeps expired Transactions from subscriptions in it's SkPaymentTransaction queues (as apposed to Google Play, which
does not return them in the list of purchases).

All Transactions, including historical transactions, are passed through to the `PurchaseObserver`.

For example, if a user has a subscription with monthly period going on for 6 months and restores purchases,
6 Transactions will be passed too in PurchaseObserver#handleRestore().

Filter out expired purchases manually. Some pointers:

* Start time of Transaction: `Transaction#getPurchaseTime()`
* Reference to Product Information: `Transaction#getInformation()`
* Reference to Subscription period: `Information#getSubscriptionPeriod()`

Putting that together, you can calculate Transaction purchaseEndTime.

If you have Billing Grace Period enabled in App Store Connect, you should add those days to the purchaseEndTime.

This logic is covered by `Transaction#calculateSubscriptionEndDate(int billingGracePeriodInDays)`

If there are zero transactions with your calculated purchaseEndTime available, the user has cancelled his subscription
and should resubscribe.

Limitations of this method:

* payment cancellations cannot be detected
* free trial periods cannot be detected; if someone decides to start a free trial of 3 days of a one-year subscription,
the user will get the full year for free if he cancels after the first day.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
enum IosVersion {
;

static boolean isIos_7_0_orAbove() {
static boolean is_7_0_orAbove() {
return Foundation.getMajorSystemVersion() >= 7;
}

Expand Down
Loading

0 comments on commit 46b2798

Please sign in to comment.