Skip to content

Commit 5bcaf61

Browse files
authored
Address a bug with a developer payload check for the promo codes (#295)
* finally address a bug with a payload check for the promo codes * fix typo
1 parent 4829238 commit 5bcaf61

File tree

5 files changed

+46
-65
lines changed

5 files changed

+46
-65
lines changed

README.md

+1-10
Original file line numberDiff line numberDiff line change
@@ -245,16 +245,7 @@ Also notice, that you will need to call periodically `bp.loadOwnedPurchasesFromG
245245

246246
## Promo Codes Support
247247

248-
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)
249-
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).
250-
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).
251-
252-
Still, there are couple of workarounds you can use:
253-
254-
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.
255-
2. Avoid using promo codes in a purchase dialog, prefer entering these codes in Google Play's App _Redeem promo code_ menu.
256-
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.
257-
You can find a sample on how to bundle it inside your app [here](https://gist.github.com/Thomas-Vos/6d44b4920dbdc8482a2467d95f66c5df).
248+
You can use promo codes along with this library.
258249

259250
## Protection Against Fake "Markets"
260251

UPGRADING.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Upgrading Android In-App Billing v3 Library
22

3+
### Upgrading to >= 1.0.44
4+
5+
The workaround below for the promo codes should no longer be valid. Promo codes should work just fine right out of the box
6+
37
### Upgrading to >= 1.0.37
48

59
If you were supporting promo codes and faced troubled described in #156,

library/src/main/java/com/anjlab/android/iab/v3/BillingProcessor.java

+26-46
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434

3535
import com.android.vending.billing.IInAppBillingService;
3636

37-
import org.json.JSONException;
3837
import org.json.JSONObject;
3938

4039
import java.util.ArrayList;
@@ -894,24 +893,20 @@ public TransactionDetails getSubscriptionTransactionDetails(String productId)
894893
return getPurchaseTransactionDetails(productId, cachedSubscriptions);
895894
}
896895

897-
private String getDeveloperPayloadFromPurchaseData(JSONObject purchase)
896+
private String detectPurchaseTypeFromPurchaseResponseData(JSONObject purchase)
898897
{
899-
String value = null;
900-
try
898+
String purchasePayload = getPurchasePayload();
899+
// regular flow, based on developer payload
900+
if (!TextUtils.isEmpty(purchasePayload) && purchasePayload.startsWith(Constants.PRODUCT_TYPE_SUBSCRIPTION))
901901
{
902-
value = purchase.has(Constants.RESPONSE_PAYLOAD)
903-
? purchase.getString(Constants.RESPONSE_PAYLOAD) : null;
902+
return Constants.PRODUCT_TYPE_SUBSCRIPTION;
904903
}
905-
catch (JSONException e)
904+
// backup check for the promo codes (no payload available)
905+
if (purchase != null && purchase.has(Constants.RESPONSE_AUTO_RENEWING))
906906
{
907-
Log.e(LOG_TAG, "Failed to extract developer payload value!");
907+
return Constants.PRODUCT_TYPE_SUBSCRIPTION;
908908
}
909-
return value != null ? value : "";
910-
}
911-
912-
private boolean validateDeveloperPayload(String expectedValue, String actualValue)
913-
{
914-
return expectedValue.equals(actualValue);
909+
return Constants.PRODUCT_TYPE_MANAGED;
915910
}
916911

917912
public boolean handleActivityResult(int requestCode, int resultCode, Intent data)
@@ -927,54 +922,40 @@ public boolean handleActivityResult(int requestCode, int resultCode, Intent data
927922
}
928923
int responseCode = data.getIntExtra(Constants.RESPONSE_CODE, Constants.BILLING_RESPONSE_RESULT_OK);
929924
Log.d(LOG_TAG, String.format("resultCode = %d, responseCode = %d", resultCode, responseCode));
930-
String purchasePayload = getPurchasePayload();
931925
if (resultCode == Activity.RESULT_OK &&
932-
responseCode == Constants.BILLING_RESPONSE_RESULT_OK &&
933-
!TextUtils.isEmpty(purchasePayload))
926+
responseCode == Constants.BILLING_RESPONSE_RESULT_OK)
934927
{
935928
String purchaseData = data.getStringExtra(Constants.INAPP_PURCHASE_DATA);
936929
String dataSignature = data.getStringExtra(Constants.RESPONSE_INAPP_SIGNATURE);
937930
try
938931
{
939932
JSONObject purchase = new JSONObject(purchaseData);
940933
String productId = purchase.getString(Constants.RESPONSE_PRODUCT_ID);
941-
String developerPayload = getDeveloperPayloadFromPurchaseData(purchase);
942-
boolean purchasedSubscription =
943-
purchasePayload.startsWith(Constants.PRODUCT_TYPE_SUBSCRIPTION);
944-
if (validateDeveloperPayload(purchasePayload, developerPayload))
934+
if (verifyPurchaseSignature(productId, purchaseData, dataSignature))
945935
{
946-
if (verifyPurchaseSignature(productId, purchaseData, dataSignature))
947-
{
948-
BillingCache cache =
949-
purchasedSubscription ? cachedSubscriptions : cachedProducts;
950-
cache.put(productId, purchaseData, dataSignature);
951-
if (eventHandler != null)
952-
{
953-
eventHandler.onProductPurchased(productId,
954-
new TransactionDetails(new PurchaseInfo(
955-
purchaseData,
956-
dataSignature)));
957-
}
958-
}
959-
else
936+
String purchaseType = detectPurchaseTypeFromPurchaseResponseData(purchase);
937+
BillingCache cache = purchaseType.equals(Constants.PRODUCT_TYPE_SUBSCRIPTION)
938+
? cachedSubscriptions : cachedProducts;
939+
cache.put(productId, purchaseData, dataSignature);
940+
if (eventHandler != null)
960941
{
961-
Log.e(LOG_TAG, "Public key signature doesn't match!");
962-
reportBillingError(Constants.BILLING_ERROR_INVALID_SIGNATURE, null);
942+
eventHandler.onProductPurchased(
943+
productId,
944+
new TransactionDetails(new PurchaseInfo(purchaseData, dataSignature)));
963945
}
964946
}
965-
else
966-
{
967-
Log.e(LOG_TAG, String.format("Payload mismatch: %s != %s",
968-
purchasePayload,
969-
developerPayload));
970-
reportBillingError(Constants.BILLING_ERROR_INVALID_DEVELOPER_PAYLOAD, null);
971-
}
947+
else
948+
{
949+
Log.e(LOG_TAG, "Public key signature doesn't match!");
950+
reportBillingError(Constants.BILLING_ERROR_INVALID_SIGNATURE, null);
951+
}
972952
}
973953
catch (Exception e)
974954
{
975955
Log.e(LOG_TAG, "Error in handleActivityResult", e);
976956
reportBillingError(Constants.BILLING_ERROR_OTHER_ERROR, e);
977957
}
958+
savePurchasePayload(null);
978959
}
979960
else
980961
{
@@ -983,8 +964,7 @@ public boolean handleActivityResult(int requestCode, int resultCode, Intent data
983964
return true;
984965
}
985966

986-
private boolean verifyPurchaseSignature(String productId, String purchaseData,
987-
String dataSignature)
967+
private boolean verifyPurchaseSignature(String productId, String purchaseData, String dataSignature)
988968
{
989969
try
990970
{

library/src/main/java/com/anjlab/android/iab/v3/Constants.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,19 @@ public class Constants
6363
public static final String INAPP_DATA_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
6464
public static final String RESPONSE_ORDER_ID = "orderId";
6565
public static final String RESPONSE_PRODUCT_ID = "productId";
66+
public static final String RESPONSE_PACKAGE_NAME = "packageName";
67+
public static final String RESPONSE_PURCHASE_TIME = "purchaseTime";
68+
public static final String RESPONSE_PURCHASE_STATE = "purchaseState";
69+
public static final String RESPONSE_PURCHASE_TOKEN = "purchaseToken";
70+
public static final String RESPONSE_DEVELOPER_PAYLOAD = "developerPayload";
6671
public static final String RESPONSE_TYPE = "type";
6772
public static final String RESPONSE_TITLE = "title";
6873
public static final String RESPONSE_DESCRIPTION = "description";
6974
public static final String RESPONSE_PRICE = "price";
7075
public static final String RESPONSE_PRICE_CURRENCY = "price_currency_code";
7176
public static final String RESPONSE_PRICE_MICROS = "price_amount_micros";
72-
public static final String RESPONSE_PAYLOAD = "developerPayload";
7377
public static final String RESPONSE_SUBSCRIPTION_PERIOD = "subscriptionPeriod";
78+
public static final String RESPONSE_AUTO_RENEWING = "autoRenewing";
7479
public static final String RESPONSE_FREE_TRIAL_PERIOD = "freeTrialPeriod";
7580
public static final String RESPONSE_INTRODUCTORY_PRICE = "introductoryPrice";
7681
public static final String RESPONSE_INTRODUCTORY_PRICE_MICROS = "introductoryPriceAmountMicros";
@@ -82,6 +87,7 @@ public class Constants
8287
public static final int BILLING_ERROR_INVALID_SIGNATURE = 102;
8388
public static final int BILLING_ERROR_LOST_CONTEXT = 103;
8489
public static final int BILLING_ERROR_INVALID_MERCHANT_ID = 104;
90+
@Deprecated
8591
public static final int BILLING_ERROR_INVALID_DEVELOPER_PAYLOAD = 105;
8692
public static final int BILLING_ERROR_OTHER_ERROR = 110;
8793
public static final int BILLING_ERROR_CONSUME_FAILED = 111;

library/src/main/java/com/anjlab/android/iab/v3/PurchaseInfo.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ PurchaseData parseResponseDataImpl()
6262
{
6363
JSONObject json = new JSONObject(responseData);
6464
PurchaseData data = new PurchaseData();
65-
data.orderId = json.optString("orderId");
66-
data.packageName = json.optString("packageName");
67-
data.productId = json.optString("productId");
68-
long purchaseTimeMillis = json.optLong("purchaseTime", 0);
65+
data.orderId = json.optString(Constants.RESPONSE_ORDER_ID);
66+
data.packageName = json.optString(Constants.RESPONSE_PACKAGE_NAME);
67+
data.productId = json.optString(Constants.RESPONSE_PRODUCT_ID);
68+
long purchaseTimeMillis = json.optLong(Constants.RESPONSE_PURCHASE_TIME, 0);
6969
data.purchaseTime = purchaseTimeMillis != 0 ? new Date(purchaseTimeMillis) : null;
70-
data.purchaseState = PurchaseState.values()[json.optInt("purchaseState", 1)];
71-
data.developerPayload = json.optString("developerPayload");
72-
data.purchaseToken = json.getString("purchaseToken");
73-
data.autoRenewing = json.optBoolean("autoRenewing");
70+
data.purchaseState = PurchaseState.values()[json.optInt(Constants.RESPONSE_PURCHASE_STATE, 1)];
71+
data.developerPayload = json.optString(Constants.RESPONSE_DEVELOPER_PAYLOAD);
72+
data.purchaseToken = json.getString(Constants.RESPONSE_PURCHASE_TOKEN);
73+
data.autoRenewing = json.optBoolean(Constants.RESPONSE_AUTO_RENEWING);
7474
return data;
7575
}
7676
catch (JSONException e)

0 commit comments

Comments
 (0)