Skip to content

Commit

Permalink
Address a bug with a developer payload check for the promo codes (anj…
Browse files Browse the repository at this point in the history
…lab#295)

* finally address a bug with a payload check for the promo codes

* fix typo
  • Loading branch information
serggl authored Aug 7, 2017
1 parent 648fde6 commit be1730d
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 65 deletions.
11 changes: 1 addition & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,16 +245,7 @@ Also notice, that you will need to call periodically `bp.loadOwnedPurchasesFromG

## Promo Codes Support

You can use promo codes along with this library, they are supported with one extra notice. According to [this issue](https://github.com/googlesamples/android-play-billing/issues/7)
there is currently a bug in Google Play Services, and it does not respect _Developer Payload_ token made by this library, causing a security validation fault (`BILLING_ERROR_INVALID_DEVELOPER_PAYLOAD` error code).
While Google engineers are working on fixing this (lets hope so, you can also leave a feedback on this issue to make them work faster).

Still, there are couple of workarounds you can use:

1. Handle `BILLING_ERROR_INVALID_DEVELOPER_PAYLOAD` error code in your `onBillingError` implementation. You can check out [#156](https://github.com/anjlab/android-inapp-billing-v3/issues/156) for a suggested workaround. This does not look nice, but it works.
2. Avoid using promo codes in a purchase dialog, prefer entering these codes in Google Play's App _Redeem promo code_ menu.
One way to do this is to distribute your promo codes in form of a redeem link (`https://play.google.com/redeem?code=YOURPROMOCODE`) instead of just a `YOURPROMOCODE` values.
You can find a sample on how to bundle it inside your app [here](https://gist.github.com/Thomas-Vos/6d44b4920dbdc8482a2467d95f66c5df).
You can use promo codes along with this library.

## Protection Against Fake "Markets"

Expand Down
4 changes: 4 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Upgrading Android In-App Billing v3 Library

### Upgrading to >= 1.0.44

The workaround below for the promo codes should no longer be valid. Promo codes should work just fine right out of the box

### Upgrading to >= 1.0.37

If you were supporting promo codes and faced troubled described in #156,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@

import com.android.vending.billing.IInAppBillingService;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
Expand Down Expand Up @@ -894,24 +893,20 @@ public TransactionDetails getSubscriptionTransactionDetails(String productId)
return getPurchaseTransactionDetails(productId, cachedSubscriptions);
}

private String getDeveloperPayloadFromPurchaseData(JSONObject purchase)
private String detectPurchaseTypeFromPurchaseResponseData(JSONObject purchase)
{
String value = null;
try
String purchasePayload = getPurchasePayload();
// regular flow, based on developer payload
if (!TextUtils.isEmpty(purchasePayload) && purchasePayload.startsWith(Constants.PRODUCT_TYPE_SUBSCRIPTION))
{
value = purchase.has(Constants.RESPONSE_PAYLOAD)
? purchase.getString(Constants.RESPONSE_PAYLOAD) : null;
return Constants.PRODUCT_TYPE_SUBSCRIPTION;
}
catch (JSONException e)
// backup check for the promo codes (no payload available)
if (purchase != null && purchase.has(Constants.RESPONSE_AUTO_RENEWING))
{
Log.e(LOG_TAG, "Failed to extract developer payload value!");
return Constants.PRODUCT_TYPE_SUBSCRIPTION;
}
return value != null ? value : "";
}

private boolean validateDeveloperPayload(String expectedValue, String actualValue)
{
return expectedValue.equals(actualValue);
return Constants.PRODUCT_TYPE_MANAGED;
}

public boolean handleActivityResult(int requestCode, int resultCode, Intent data)
Expand All @@ -927,54 +922,40 @@ public boolean handleActivityResult(int requestCode, int resultCode, Intent data
}
int responseCode = data.getIntExtra(Constants.RESPONSE_CODE, Constants.BILLING_RESPONSE_RESULT_OK);
Log.d(LOG_TAG, String.format("resultCode = %d, responseCode = %d", resultCode, responseCode));
String purchasePayload = getPurchasePayload();
if (resultCode == Activity.RESULT_OK &&
responseCode == Constants.BILLING_RESPONSE_RESULT_OK &&
!TextUtils.isEmpty(purchasePayload))
responseCode == Constants.BILLING_RESPONSE_RESULT_OK)
{
String purchaseData = data.getStringExtra(Constants.INAPP_PURCHASE_DATA);
String dataSignature = data.getStringExtra(Constants.RESPONSE_INAPP_SIGNATURE);
try
{
JSONObject purchase = new JSONObject(purchaseData);
String productId = purchase.getString(Constants.RESPONSE_PRODUCT_ID);
String developerPayload = getDeveloperPayloadFromPurchaseData(purchase);
boolean purchasedSubscription =
purchasePayload.startsWith(Constants.PRODUCT_TYPE_SUBSCRIPTION);
if (validateDeveloperPayload(purchasePayload, developerPayload))
if (verifyPurchaseSignature(productId, purchaseData, dataSignature))
{
if (verifyPurchaseSignature(productId, purchaseData, dataSignature))
{
BillingCache cache =
purchasedSubscription ? cachedSubscriptions : cachedProducts;
cache.put(productId, purchaseData, dataSignature);
if (eventHandler != null)
{
eventHandler.onProductPurchased(productId,
new TransactionDetails(new PurchaseInfo(
purchaseData,
dataSignature)));
}
}
else
String purchaseType = detectPurchaseTypeFromPurchaseResponseData(purchase);
BillingCache cache = purchaseType.equals(Constants.PRODUCT_TYPE_SUBSCRIPTION)
? cachedSubscriptions : cachedProducts;
cache.put(productId, purchaseData, dataSignature);
if (eventHandler != null)
{
Log.e(LOG_TAG, "Public key signature doesn't match!");
reportBillingError(Constants.BILLING_ERROR_INVALID_SIGNATURE, null);
eventHandler.onProductPurchased(
productId,
new TransactionDetails(new PurchaseInfo(purchaseData, dataSignature)));
}
}
else
{
Log.e(LOG_TAG, String.format("Payload mismatch: %s != %s",
purchasePayload,
developerPayload));
reportBillingError(Constants.BILLING_ERROR_INVALID_DEVELOPER_PAYLOAD, null);
}
else
{
Log.e(LOG_TAG, "Public key signature doesn't match!");
reportBillingError(Constants.BILLING_ERROR_INVALID_SIGNATURE, null);
}
}
catch (Exception e)
{
Log.e(LOG_TAG, "Error in handleActivityResult", e);
reportBillingError(Constants.BILLING_ERROR_OTHER_ERROR, e);
}
savePurchasePayload(null);
}
else
{
Expand All @@ -983,8 +964,7 @@ public boolean handleActivityResult(int requestCode, int resultCode, Intent data
return true;
}

private boolean verifyPurchaseSignature(String productId, String purchaseData,
String dataSignature)
private boolean verifyPurchaseSignature(String productId, String purchaseData, String dataSignature)
{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,19 @@ public class Constants
public static final String INAPP_DATA_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
public static final String RESPONSE_ORDER_ID = "orderId";
public static final String RESPONSE_PRODUCT_ID = "productId";
public static final String RESPONSE_PACKAGE_NAME = "packageName";
public static final String RESPONSE_PURCHASE_TIME = "purchaseTime";
public static final String RESPONSE_PURCHASE_STATE = "purchaseState";
public static final String RESPONSE_PURCHASE_TOKEN = "purchaseToken";
public static final String RESPONSE_DEVELOPER_PAYLOAD = "developerPayload";
public static final String RESPONSE_TYPE = "type";
public static final String RESPONSE_TITLE = "title";
public static final String RESPONSE_DESCRIPTION = "description";
public static final String RESPONSE_PRICE = "price";
public static final String RESPONSE_PRICE_CURRENCY = "price_currency_code";
public static final String RESPONSE_PRICE_MICROS = "price_amount_micros";
public static final String RESPONSE_PAYLOAD = "developerPayload";
public static final String RESPONSE_SUBSCRIPTION_PERIOD = "subscriptionPeriod";
public static final String RESPONSE_AUTO_RENEWING = "autoRenewing";
public static final String RESPONSE_FREE_TRIAL_PERIOD = "freeTrialPeriod";
public static final String RESPONSE_INTRODUCTORY_PRICE = "introductoryPrice";
public static final String RESPONSE_INTRODUCTORY_PRICE_MICROS = "introductoryPriceAmountMicros";
Expand All @@ -82,6 +87,7 @@ public class Constants
public static final int BILLING_ERROR_INVALID_SIGNATURE = 102;
public static final int BILLING_ERROR_LOST_CONTEXT = 103;
public static final int BILLING_ERROR_INVALID_MERCHANT_ID = 104;
@Deprecated
public static final int BILLING_ERROR_INVALID_DEVELOPER_PAYLOAD = 105;
public static final int BILLING_ERROR_OTHER_ERROR = 110;
public static final int BILLING_ERROR_CONSUME_FAILED = 111;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ PurchaseData parseResponseDataImpl()
{
JSONObject json = new JSONObject(responseData);
PurchaseData data = new PurchaseData();
data.orderId = json.optString("orderId");
data.packageName = json.optString("packageName");
data.productId = json.optString("productId");
long purchaseTimeMillis = json.optLong("purchaseTime", 0);
data.orderId = json.optString(Constants.RESPONSE_ORDER_ID);
data.packageName = json.optString(Constants.RESPONSE_PACKAGE_NAME);
data.productId = json.optString(Constants.RESPONSE_PRODUCT_ID);
long purchaseTimeMillis = json.optLong(Constants.RESPONSE_PURCHASE_TIME, 0);
data.purchaseTime = purchaseTimeMillis != 0 ? new Date(purchaseTimeMillis) : null;
data.purchaseState = PurchaseState.values()[json.optInt("purchaseState", 1)];
data.developerPayload = json.optString("developerPayload");
data.purchaseToken = json.getString("purchaseToken");
data.autoRenewing = json.optBoolean("autoRenewing");
data.purchaseState = PurchaseState.values()[json.optInt(Constants.RESPONSE_PURCHASE_STATE, 1)];
data.developerPayload = json.optString(Constants.RESPONSE_DEVELOPER_PAYLOAD);
data.purchaseToken = json.getString(Constants.RESPONSE_PURCHASE_TOKEN);
data.autoRenewing = json.optBoolean(Constants.RESPONSE_AUTO_RENEWING);
return data;
}
catch (JSONException e)
Expand Down

0 comments on commit be1730d

Please sign in to comment.