Skip to content

Commit

Permalink
Support for getBuyIntentExtraParams() (#287)
Browse files Browse the repository at this point in the history
* Add support for getBuyIntentExtraParams()

Add support for getBuyIntentExtraParams() as this is the recommended method by Google
https://developer.android.com/google/play/billing/billing_reference.html#getBuyIntent

The method is a variant of the getBuyIntent() method, and takes an additional extraParams parameter.

More info here: https://developer.android.com/google/play/billing/billing_reference.html#getBuyIntentExtraParams

* Cleanup

* Address coding style issues

* Remove extra blank line

* As requested, create specific methods to send the extra params, instead of overriding existing ones

* Fix style

* Implement @serggl’s feedback

Use isBillingSupportedExtraParams() to check if we can use the new getBuyIntentExtraParams() method and fallback to the previous method if that’s not the case.

As isBillingSupportedExtraParams() is only available starting with Billing API v7, we effectively limit the usage of getBuyIntentExtraParams() to Billing API v7+

* Add missing javadoc + Fix style

* Update README to add new methods usage
  • Loading branch information
ratm authored and serggl committed Jul 24, 2017
1 parent deb8d28 commit c8e0cd8
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 17 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ bp.purchase(YOUR_ACTIVITY, "YOUR PRODUCT ID FROM GOOGLE PLAY CONSOLE HERE", "DEV
bp.subscribe(YOUR_ACTIVITY, "YOUR SUBSCRIPTION ID FROM GOOGLE PLAY CONSOLE HERE", "DEVELOPER PAYLOAD HERE");
```
_IMPORTANT: when you provide a payload, internally the library prepends a string to your payload. For subscriptions, it prepends `"subs:\<productId\>:"`, and for products, it prepends `"inapp:\<productId\>:\<UUID\>:"`. This is important to know if you do any validation on the payload returned from Google Play after a successful purchase._

_With a bundle of extra parameters:_

```java
Bundle extraParams = new Bundle()
extraParams.putString("accountId", "MY_ACCOUNT_ID");
bp.purchase(YOUR_ACTIVITY, "YOUR PRODUCT ID FROM GOOGLE PLAY CONSOLE HERE", null /*or developer payload*/, extraParams);
bp.subscribe(YOUR_ACTIVITY, "YOUR SUBSCRIPTION ID FROM GOOGLE PLAY CONSOLE HERE", null /*or developer payload*/, extraParams);
```

Use these methods if you want to pass extra parameters, [as documented here](https://developer.android.com/google/play/billing/billing_reference.html#getBuyIntentExtraParams), you can provide a Bundle object.

_Please note that this feature is only available if the target device is support the version 7 of the In App billing API._

* **That's it! A super small and fast in-app library ever!**

* **And don't forget**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012 Google Inc.
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -108,7 +108,6 @@ interface IInAppBillingService {
* "developerPayload":"example developer payload" }'
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
* was signed with the private key of the developer
* TODO: change this to app-specific keys.
*/
Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
String developerPayload);
Expand Down Expand Up @@ -185,8 +184,98 @@ interface IInAppBillingService {
* "developerPayload":"example developer payload" }'
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
* was signed with the private key of the developer
* TODO: change this to app-specific keys.
*/
Bundle getBuyIntentToReplaceSkus(int apiVersion, String packageName,
in List<String> oldSkus, String newSku, String type, String developerPayload);

/**
* Returns a pending intent to launch the purchase flow for an in-app item. This method is
* a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams}
* parameter. This parameter is a Bundle of optional keys and values that affect the
* operation of the method.
* @param apiVersion billing API version that the app is using, must be 6 or later
* @param packageName package name of the calling app
* @param sku the SKU of the in-app item as published in the developer console
* @param type of the in-app item being purchased ("inapp" for one-time purchases
* and "subs" for subscriptions)
* @param developerPayload optional argument to be sent back with the purchase information
* @extraParams a Bundle with the following optional keys:
* "skusToReplace" - List<String> - an optional list of SKUs that the user is
* upgrading or downgrading from.
* Pass this field if the purchase is upgrading or downgrading
* existing subscriptions.
* The specified SKUs are replaced with the SKUs that the user is
* purchasing. Google Play replaces the specified SKUs at the start of
* the next billing cycle.
* "replaceSkusProration" - Boolean - whether the user should be credited for any unused
* subscription time on the SKUs they are upgrading or downgrading.
* If you set this field to true, Google Play swaps out the old SKUs
* and credits the user with the unused value of their subscription
* time on a pro-rated basis.
* Google Play applies this credit to the new subscription, and does
* not begin billing the user for the new subscription until after
* the credit is used up.
* If you set this field to false, the user does not receive credit for
* any unused subscription time and the recurrence date does not
* change.
* Default value is true. Ignored if you do not pass skusToReplace.
* "accountId" - String - an optional obfuscated string that is uniquely
* associated with the user's account in your app.
* If you pass this value, Google Play can use it to detect irregular
* activity, such as many devices making purchases on the same
* account in a short period of time.
* Do not use the developer ID or the user's Google ID for this field.
* In addition, this field should not contain the user's ID in
* cleartext.
* We recommend that you use a one-way hash to generate a string from
* the user's ID, and store the hashed string in this field.
* "vr" - Boolean - an optional flag indicating whether the returned intent
* should start a VR purchase flow. The apiVersion must also be 7 or
* later to use this flag.
*/
Bundle getBuyIntentExtraParams(int apiVersion, String packageName, String sku,
String type, String developerPayload, in Bundle extraParams);

/**
* Returns the most recent purchase made by the user for each SKU, even if that purchase is
* expired, canceled, or consumed.
* @param apiVersion billing API version that the app is using, must be 6 or later
* @param packageName package name of the calling app
* @param type of the in-app items being requested ("inapp" for one-time purchases
* and "subs" for subscriptions)
* @param continuationToken to be set as null for the first call, if the number of owned
* skus is too large, a continuationToken is returned in the response bundle.
* This method can be called again with the continuation token to get the next set of
* owned skus.
* @param extraParams a Bundle with extra params that would be appended into http request
* query string. Not used at this moment. Reserved for future functionality.
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value: RESULT_OK(0) if success,
* {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures.
*
* "INAPP_PURCHASE_ITEM_LIST" - ArrayList<String> containing the list of SKUs
* "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information
* "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> containing the signatures
* of the purchase information
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
* next set of in-app purchases. Only set if the
* user has more owned skus than the current list.
*/
Bundle getPurchaseHistory(int apiVersion, String packageName, String type,
String continuationToken, in Bundle extraParams);

/**
* This method is a variant of {@link #isBillingSupported}} that takes an additional
* {@code extraParams} parameter.
* @param apiVersion billing API version that the app is using, must be 7 or later
* @param packageName package name of the calling app
* @param type of the in-app item being purchased ("inapp" for one-time purchases and "subs"
* for subscriptions)
* @param extraParams a Bundle with the following optional keys:
* "vr" - Boolean - an optional flag to indicate whether {link #getBuyIntentExtraParams}
* supports returning a VR purchase flow.
* @return RESULT_OK(0) on success and appropriate response code on failures.
*/
int isBillingSupportedExtraParams(int apiVersion, String packageName, String type,
in Bundle extraParams);
}
195 changes: 181 additions & 14 deletions library/src/main/java/com/anjlab/android/iab/v3/BillingProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public interface IBillingHandler
private String developerMerchantId;
private boolean isOneTimePurchasesSupported;
private boolean isSubsUpdateSupported;
private boolean isSubscriptionExtraParamsSupported;
private boolean isOneTimePurchaseExtraParamsSupported;

private class HistoryInitializationTask extends AsyncTask<Void, Void, Boolean>
{
Expand Down Expand Up @@ -303,12 +305,12 @@ public boolean loadOwnedPurchasesFromGoogle()

public boolean purchase(Activity activity, String productId)
{
return purchase(activity, productId, Constants.PRODUCT_TYPE_MANAGED, null);
return purchase(activity, null, productId, Constants.PRODUCT_TYPE_MANAGED, null);
}

public boolean subscribe(Activity activity, String productId)
{
return purchase(activity, productId, Constants.PRODUCT_TYPE_SUBSCRIPTION, null);
return purchase(activity, null, productId, Constants.PRODUCT_TYPE_SUBSCRIPTION, null);
}

public boolean purchase(Activity activity, String productId, String developerPayload)
Expand All @@ -321,6 +323,53 @@ public boolean subscribe(Activity activity, String productId, String developerPa
return purchase(activity, productId, Constants.PRODUCT_TYPE_SUBSCRIPTION, developerPayload);
}

/***
* Purchase a product
*
* @param activity the activity calling this method
* @param productId the product id to purchase
* @param extraParamsBundle A bundle object containing extra parameters to pass to
* getBuyIntentExtraParams()
* @see <a href="https://developer.android.com/google/play/billing/billing_reference.html#getBuyIntentExtraParams">extra
* params documentation on developer.android.com</a>
* @return {@code false} if the billing system is not initialized, {@code productId} is empty
* or if an exception occurs. Will return {@code true} otherwise.
*/
public boolean purchase(Activity activity, String productId, String developerPayload, Bundle extraParams)
{
if(!isOneTimePurchaseWithExtraParamsSupported(extraParams))
{
return purchase(activity, productId, developerPayload);
}
else
{
return purchase(activity, null, productId, Constants.PRODUCT_TYPE_MANAGED, developerPayload, extraParams);
}
}

/**
* Subscribe to a product
*
* @param activity the activity calling this method
* @param productId the product id to purchase
* @param extraParamsBundle A bundle object containing extra parameters to pass to getBuyIntentExtraParams()
* @see <a href="https://developer.android.com/google/play/billing/billing_reference.html#getBuyIntentExtraParams">extra
* params documentation on developer.android.com</a>
* @return {@code false} if the billing system is not initialized, {@code productId} is empty or if an exception occurs.
* Will return {@code true} otherwise.
*/
public boolean subscribe(Activity activity, String productId, String developerPayload, Bundle extraParams)
{
if (!isSubscriptionWithExtraParamsSupported(extraParams))
{
return purchase(activity, null, productId, Constants.PRODUCT_TYPE_SUBSCRIPTION, developerPayload);
}
else
{
return purchase(activity, null, productId, Constants.PRODUCT_TYPE_SUBSCRIPTION, developerPayload, extraParams);
}
}

public boolean isOneTimePurchaseSupported()
{
if (isOneTimePurchasesSupported)
Expand Down Expand Up @@ -364,6 +413,62 @@ public boolean isSubscriptionUpdateSupported()
return isSubsUpdateSupported;
}

/**
* Check API v7 support for subscriptions
* @param extraParams
* @return {@code true} if the current API supports calling getBuyIntentExtraParams() for
* subscriptions, {@code false} otherwise.
*/
public boolean isSubscriptionWithExtraParamsSupported(Bundle extraParams)
{
if(isSubscriptionExtraParamsSupported)
{
return true;
}

try
{
int response =
billingService.isBillingSupportedExtraParams(Constants.GOOGLE_API_VR_SUPPORTED_VERSION,
contextPackageName,
Constants.PRODUCT_TYPE_SUBSCRIPTION, extraParams);
isSubscriptionExtraParamsSupported = response == Constants.BILLING_RESPONSE_RESULT_OK;
}
catch (RemoteException e)
{
e.printStackTrace();
}
return isSubscriptionExtraParamsSupported;
}

/**
* Check API v7 support for one-time purchases
* @param extraParams
* @return {@code true} if the current API supports calling getBuyIntentExtraParams() for
* one-time purchases, {@code false} otherwise.
*/
public boolean isOneTimePurchaseWithExtraParamsSupported(Bundle extraParams)
{
if(isOneTimePurchaseExtraParamsSupported)
{
return true;
}

try
{
int response =
billingService.isBillingSupportedExtraParams(Constants.GOOGLE_API_VR_SUPPORTED_VERSION,
contextPackageName,
Constants.PRODUCT_TYPE_MANAGED, extraParams);
isOneTimePurchaseExtraParamsSupported = response == Constants.BILLING_RESPONSE_RESULT_OK;
}
catch (RemoteException e)
{
e.printStackTrace();
}
return isOneTimePurchaseExtraParamsSupported;
}

/**
* Change subscription i.e. upgrade or downgrade
*
Expand Down Expand Up @@ -433,6 +538,35 @@ public boolean updateSubscription(Activity activity, List<String> oldProductIds,
return purchase(activity, oldProductIds, productId, Constants.PRODUCT_TYPE_SUBSCRIPTION, developerPayload);
}

/**
*
* @param activity the activity calling this method
* @param oldProductIds passing null will act the same as {@link #subscribe(Activity, String)}
* @param productId the new subscription id
* @param developerPayload the developer payload
* @param extraParams A bundle object containing extra parameters to pass to getBuyIntentExtraParams()
* @see <a href="https://developer.android.com/google/play/billing/billing_reference.html#getBuyIntentExtraParams">extra
* params documentation on developer.android.com</a>
* @return {@code false} if {@code oldProductIds} is not {@code null} AND change subscription
* is not supported.
*/
public boolean updateSubscription(Activity activity, List<String> oldProductIds,
String productId, String developerPayload, Bundle extraParams)
{
if (oldProductIds != null && !isSubscriptionUpdateSupported())
{
return false;
}

// if API v7 is not supported, let's fallback to the previous method
if( !isSubscriptionWithExtraParamsSupported(extraParams))
{
return updateSubscription(activity, oldProductIds, productId, developerPayload);
}

return purchase(activity, oldProductIds, productId, Constants.PRODUCT_TYPE_SUBSCRIPTION, developerPayload, extraParams);
}

private boolean purchase(Activity activity, String productId, String purchaseType,
String developerPayload)
{
Expand All @@ -441,6 +575,12 @@ private boolean purchase(Activity activity, String productId, String purchaseTyp

private boolean purchase(Activity activity, List<String> oldProductIds, String productId,
String purchaseType, String developerPayload)
{
return purchase(activity, oldProductIds, productId, purchaseType, developerPayload, null);
}

private boolean purchase(Activity activity, List<String> oldProductIds, String productId,
String purchaseType, String developerPayload, Bundle extraParamsBundle)
{
if (!isInitialized() || TextUtils.isEmpty(productId) || TextUtils.isEmpty(purchaseType))
{
Expand All @@ -461,21 +601,48 @@ private boolean purchase(Activity activity, List<String> oldProductIds, String p
Bundle bundle;
if (oldProductIds != null && purchaseType.equals(Constants.PRODUCT_TYPE_SUBSCRIPTION))
{
bundle =
billingService.getBuyIntentToReplaceSkus(Constants.GOOGLE_API_SUBSCRIPTION_CHANGE_VERSION,
contextPackageName,
oldProductIds,
productId,
purchaseType,
purchasePayload);
if(extraParamsBundle == null) // API v5
{
bundle = billingService.getBuyIntentToReplaceSkus(Constants.GOOGLE_API_SUBSCRIPTION_CHANGE_VERSION,
contextPackageName,
oldProductIds,
productId,
purchaseType,
purchasePayload);
}
else // API v7+ supported
{
if (extraParamsBundle == null)
{
extraParamsBundle = new Bundle();
}

if (!extraParamsBundle.containsKey(Constants.EXTRA_PARAMS_KEY_SKU_TO_REPLACE))
{
extraParamsBundle.putStringArrayList(Constants.EXTRA_PARAMS_KEY_SKU_TO_REPLACE,
new ArrayList<String>(oldProductIds));
}

bundle = billingService.getBuyIntentExtraParams(Constants.GOOGLE_API_VR_SUPPORTED_VERSION,
contextPackageName, productId, purchaseType, purchasePayload, extraParamsBundle);
}
}
else
{
bundle = billingService.getBuyIntent(Constants.GOOGLE_API_VERSION,
contextPackageName,
productId,
purchaseType,
purchasePayload);
if(extraParamsBundle == null) // API v3
{
bundle = billingService.getBuyIntent(Constants.GOOGLE_API_VERSION,
contextPackageName,
productId,
purchaseType,
purchasePayload);

}
else // API v7+
{
bundle = billingService.getBuyIntentExtraParams(Constants.GOOGLE_API_VR_SUPPORTED_VERSION,
contextPackageName, productId, purchaseType, purchasePayload, extraParamsBundle);
}
}

if (bundle != null)
Expand Down
Loading

0 comments on commit c8e0cd8

Please sign in to comment.