Android in app purchase: Signature verification failed - android

I have tried for several days to solve this problem, using the Dungeons demo code that comes with the SDK. I've tried to Google for an answer but can't find one.
In the Dungeons demo, I passed my public key from the dev console.
Signed the apk and uploaded to console without publish.
Testing for both android.test.purchased & product list created on console with published for subscription (The main feature I want for my app).
But still I get an error of Signature verification failed and then the signature does not match data. How can I solve this?
public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData, String signature)
{
if (signedData == null) {
Log.e(TAG, "data is null");
return null;
}
if (Consts.DEBUG) {
Log.i(TAG, "signedData: " + signedData);
}
boolean verified = false;
if (!TextUtils.isEmpty(signature)) {
String base64EncodedPublicKey = "MIIBIjA....AQAB";
PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
verified = Security.verify(key, signedData, signature);
if (!verified) {
Log.w(TAG, "signature does not match data.");
return null;
}
}
}
public static boolean verify(PublicKey publicKey, String signedData, String signature)
{
if (Consts.DEBUG) {
Log.i(TAG, "signature: " + signature);
}
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
}
return false;
}

This problem is still going on in the current Google billing version. Basically the android.test.purchased is broken; After you buy android.test.purchased the verifyPurchase function in Security.java will always fail and the QueryInventoryFinishedListener will stop at the line if (result.isFailure()); this is because the android.test.purchased item always fails the TextUtils.isEmpty(signature) check in Security.java as it is not a real item and has no signature returned by the server.
My advice (from lack of any other solution) is to NEVER use "android.test.purchased". There are various code tweaks on the net but none of them work 100%.
If you have used the android.test.purchased then one way to get rid of the error is to do the following:-
Edit Security.java and change the "return false" line in the verifyPurchase to "return true" - this is temporary, we'll be putting it back in a minute.
In your QueryInventoryFinishedListener, after the "if (result.isFailure()) {...}" lines add the following to consume and get rid of your never ending android.test.purchased item:
if (inventory.hasPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD)) {
mHelper.consumeAsync(inventory.getPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD),null);
}
Run your app so the consunmeAsync happens, this gets rid of the "android.test.purchased" item on the server.
Remove the consumeAsync code (or comment it out).
Back in the Security.java, change the "return true" back to "return false".
Your QueryInventoryFinishedListener will no longer error on the verify, everything is back to "normal" (if you can call it that). Remember - don't bother using android.test.purchased again as it will just cause this error again... it's broke! The only real way to test your purchasing it to upload an APK, wait for it to appear, and then test it (the same APK) on your device with logging enabled.

Yes, the problem still occurs.
After I bought android.test.purchased I start getting the error on quering the inventory.
It is possible to fix your phone by just clearing data of Google Play Store application and running Google Play one time.
When you clear data of Google Play it forgets that you bought android.test.purchased

Please check that base64EncodedPublicKey and the one from the Play Developer Console are equal.
Once you re-upload the APK in the Developer Console, the public key may change, if so update your base64EncodedPublicKey.

You can skip the verifying process for those "android.test.*" product ids. If you are using the sample code from the TrivialDrive example, open IabHelper.java, find the following line code, change it from
if (Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) { ... }
into
boolean verifySignature = !sku.startsWith("android.test."); // or inplace the condition in the following line
if (verifySignature && !Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) { ... }
It's harmless, even if you forgot to rollback the code. So, you can continue to test the further workflow step.

Based on GMTDev's answer, this is what I do in order to fix the testing issues when consuming products in the simplest possible way. In Security.java, replace the verifyPurchase() method with this:
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.");
return BuildConfig.DEBUG; // Line modified by Cristian. Original line was: return false;
}
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature);
}
I only modified one line (see comment), and this way you can keep the code like that for debugging and still publish your release versions safely.

The error is caused because of the wrong license key. Maybe the license key is probably from your another app.
The solution is to use the proper license key from :
Play console > App > Development Tools > Licensing & in-app billing

What worked for me, while using In-app Billing v3 and the included utility classes, was consuming the test purchase within the returned onActivityResult call.
No changes to IabHelper, Security, or any of the In-app Billing util classes are needed to avoid this for future test purchases.
If you have already tried purchasing the test product and are now stuck on the purchase signature verification failed error, which you likely are since you are looking up answers for this error, then you should:
make the changes that GMTDev recommended
run the app to ensure that it consumes the product
remove/undo GMTDev's changes
implement the code below within onActivityResult.
Not only does this allow for the purchase testing process to be fluid but this should also avoid any conflicting issues with iab returning the " Item Already Owned " error when attempting to repurchase the test product.
If this is being called from within a fragment and your fragment's onActivityResult isn't being called then be sure to call YourFragmentName.onActivityResult(requestCode, resultCode, data) from your parent ActivityFragment if necessary. This is explained in more detail in Calling startIntentSenderForResult from Fragment (Android Billing v3).
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_PURCHASE) {
//this ensures that the mHelper.flagEndAsync() gets called
//prior to starting a new async request.
mHelper.handleActivityResult(requestCode, resultCode, data);
//get needed data from Intent extra to recreate product object
int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
// Strip out getActivity() if not being used within a fragment
if (resultCode == getActivity().RESULT_OK) {
try {
JSONObject jo = new JSONObject(purchaseData);
String sku = jo.getString("productId");
//only auto consume the android.test.purchased product
if (sku.equals("android.test.purchased")) {
//build the purchase object from the response data
Purchase purchase = new Purchase("inapp", purchaseData, dataSignature);
//consume android.test.purchased
mHelper.consumeAsync(purchase,null);
}
} catch (JSONException je) {
//failed to parse the purchase data
je.printStackTrace();
} catch (IllegalStateException ise) {
//most likely either disposed, not setup, or
//another billing async process is already running
ise.printStackTrace();
} catch (Exception e) {
//unexpected error
e.printStackTrace();
}
}
}
}
It will only remove the purchase if it's sku is "android.test.purchased" so it should be safe to use.

This Solution worked for me. I changed the new verifyPurchase method in purchase class with old one.

Signature verification fails only for the default test product.
A quick fix :
Goto IabHelper class.
Invert the if conditions of Security.verifyPurchase.
Thats it!
Remember to revert the changes when test product is replaced by actual product

Ran into the same issue (signature verification, and getting rid of the test purchase) today (Oct 30, 2018).
The signature issue is probably being caused by the fact that these test sku's are not really part of your app, and are thus do not have your app's signature. I did open a ticket with Google, but not sure if they can fix this. The workaround, as others pointed out, is to replace the code
if (verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {
with
if (verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature()) ||
(purchase.getSku().startsWith("android.test.")) ) {
Regarding "how to get rid of the purchase of android.test.purchased SKU", I found that a simple reboot of the device, followed by waiting for a minute or so and/or re-starting your app a couple of times fixed it for me (i.e. I didn't have to 'consume' the purchase by code). I am guessing that the wait is needed so that the Play store completes syncing with Google's servers. (Not sure if this will continue to work this way in the future, but if it works for you now, this might help you move forward.)

Check this answer:
Is the primary account on your test device the same as your Google
Play developer account?
If not you won't get signatures on the android.test.* static responses
unless the app has been published on Play before.
See the table at
http://developer.android.com/guide/market/billing/billing_testing.html#static-responses-table
for the full set of conditions.
And it's comment:
I don't think the static ids return signature anymore. See
https://groups.google.com/d/topic/android-developers/PCbCJdOl480/discussion
Also, previously the sample code (used by many big apps) from Google Play Billing Library allowed an empty signature. That's why it's static purchases worked there.
But it was a security hole, so when it was published, Google submitted an update.

I have the same problem and follow #Deadolus said based on https://www.gaffga.de/implementing-in-app-billing-for-android/
The key point is we need to make the SKU is consumable even the inventory query result is failed. Below is the sample how i did that.
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;
// Is it a failure?
if (result.isFailure()) {
try {
Purchase purchase = new Purchase("inapp", "{\"packageName\":\"PACKAGE_NAME\","+
"\"orderId\":\"transactionId.android.test.purchased\","+
"\"productId\":\"android.test.purchased\",\"developerPayload\":\"\",\"purchaseTime\":0,"+
"\"purchaseState\":0,\"purchaseToken\":\"inapp:PACKAGE_NAME :android.test.purchased\"}",
"");
} catch (JSONException e) {
e.printStackTrace();
}
mHelper.consumeAsync(purchase, null);
complain("Failed to query inventory: " + result);
return;
}
Log.d(TAG, "Query inventory was successful.");
/*
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it's correct! See
* verifyDeveloperPayload().
*/
}
};
Replace PACKAGE_NAME in the code above with the package name of your app.

This is what worked for me:
Call BillingClient.querySkuDetailsAsync to query if item if available
Wait for SkuDetailsResponseListener.onSkuDetailsResponse
Wait another 500ms
Start purchase using BillingClient.launchBillingFlow...
The step 3 shouldn't be necessary because when I received onSkuDetailsResponse it should be OK but it isn't, had to wait a little bit. After that purchase works, no more "Item not available error". This is how I tested it:
clear my app data
clear Google Play data
run app
purchase android.test.purchased
try to purchase my items (it fails with item not available)
use my solution above, it works

For Cordova and Hybrid apps you need to use this.iap.subscribe(this.productId) method to subscription InAppPurchase.
Following are the code working fine for me:
getProdutIAP() {
this.navCtrl.push('subscribeDialogPage');
this.iap
.getProducts(['productID1']).then((products: any) => {
this.buy(products);
})
.catch((err) => {
console.log(JSON.stringify(err));
alert('Finished Purchase' + JSON.stringify(err));
console.log(err);
});
}
buy(products: any) {
// this.getProdutIAP();
// alert(products[0].productId);
this.iap.subscribe(products[0].productId).then((buydata: any) => {
alert('buy Purchase' + JSON.stringify(buydata));
// this.sub();
}).catch((err) => {
// this.navCtrl.push('subscribeDialogPage');
alert('buyError' + JSON.stringify(err));
});
}
sub() {
this.platform.ready().then(() => {
this.iap
.subscribe(this.productId)
.then((data) => {
console.log('subscribe Purchase' + JSON.stringify(data));
alert('subscribe Purchase' + JSON.stringify(data));
this.getReceipt();
}).catch((err) => {
this.getReceipt();
alert('subscribeError' + JSON.stringify(err));
console.log(err);
});
})
}

Related

Verifying in-app purchase from Google Play

I'm working on an Azure Functions app that will make calls to Google to verify in-app purchases users make in my mobile app.
I'm using the Google.Apis.AndroidPublisher.v3 NuGet package with the following code to verify an in-app purchase.
public async Task<bool> IsValidPurchase(string bundleId, string receiptId, string purchaseToken, string developerPayload)
{
try
{
var isValidPurchase = true;
var request = _googleService.Purchases.Products.Get(bundleId, receiptId, purchaseToken);
var purchaseState = await request.ExecuteAsync();
if (purchaseState.DeveloperPayload != developerPayload)
isValidPurchase = false;
if (purchaseState.PurchaseState != 0)
isValidPurchase = false;
return isValidPurchase;
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
I keep getting "Bad Request - Invalid Value" error.
My mobile users will be purchasing auto-renewing subscriptions and after a successful purchase on an Android phone, I get the following response -- see image below:
The verification method asks for bundleId, receiptId, purchaseToken and developerPayload.
The bundleId and purchaseToken are pretty clear so I'm thinking the error I get is either due to receiptId or developerPayload.
I tried using both Id and ProductId values from the response -- see image above -- but I keep getting the same error:
Bad request - Invalid Value
Any idea what's wrong here? Am I using the wrong values? I'd appreciate some pointers here.

Android: Receiving wrong UTM from Google Ads

According to my boss, some of our applications have been invested on advertising of the app via Google Ads. In order for them to parse the data and analyze them correctly, they are using the UTM auto-tagging approach. It is my job from the client (Android Device) to send the UTM using Firebase Analytics and also a custom event to Firebase depending on this UTM.
However, our data shows that both Firebase SDK and our events are transferred incorrectly. The click numbers and the download numbers do not match. Since both of them are incorrect, I'm guessing the received UTM on the device itself is wrong, and this needs to be received correctly and I am unable to find an answer for this.
I'm using Install Referrer Library to track down what the UTM is after the app is downloaded to the device. I am guessing Firebase SDK also uses somewhat similar approach. On our end, the UTM is recorded to SharedPreferences and it is not queried again if the query was successful.
Here is the related code for it (the processReferrer method basically parses the UTM according to our needs):
/**
* Checks if the referrer information is recorded before, if not, creates
* a connection to Google Play and saves the data to shared preferences.
*/
private static void fetchReferrerInformation(Context context)
{
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context);
String utmData = preferences.getString(UTM_DATA, "");
// Only connect if utm is not recorded before.
if (TextUtils.isEmpty(utmData))
{
InstallReferrerClient client;
try
{
client = InstallReferrerClient.newBuilder(context).build();
client.startConnection(new InstallReferrerStateListener()
{
#Override
public void onInstallReferrerSetupFinished(int responseCode)
{
switch (responseCode)
{
case InstallReferrerClient.InstallReferrerResponse.OK:
{
ReferrerDetails response;
try
{
response = client.getInstallReferrer();
}
catch (Exception e)
{
Log.e(TAG, "Error while fetching referrer information.", e);
if (Fabric.isInitialized())
Crashlytics.logException(new IllegalStateException("Exception while fetching UTM information.", e));
return;
}
if (response != null)
{
processReferrer(context, response.getInstallReferrer());
}
break;
}
case InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED:
{
Log.w(TAG, "Install referrer client: Feature is not supported.");
break;
}
case InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE:
{
Log.w(TAG, "Install referrer client: Service is unavailable.");
break;
}
}
try
{
client.endConnection();
}
catch (Exception ignored){}
}
#Override
public void onInstallReferrerServiceDisconnected()
{
// Do nothing, we need to fetch the information once and
// it is not really necessary to try to reconnect.
// If app is opened once more, the attempt will be made anyway.
}
});
}
catch (Exception e)
{
if (Fabric.isInitialized())
Crashlytics.logException(new IllegalStateException("Exception while fetching UTM information.", e));
}
}
else
Log.i(TAG, "UTM is already recorded, skipping connection initialization. Value: " +
utmData);
}
The approach is pretty simple, however the data seems to be wrong. So, does it seem that the implementation is somewhat incorrect? If not, why is the data received from Google Ads is wrong? Any help is appreciated, thank you very much.
Edit: Upon some testing, here is what I've found:
Works:
An API 19 real device (GM Discovery II Mini) and in between API 25-29 emulators with Play Store installed. Edit: UTM can also be fetched with API 23 and 24 Genymotion Emulators, where Play Store is installed.
Doesn't work:
An API 24 Android Studio emulator with latest Google Play Services and Play Store installed (device is also logged in to my account), and a real device (General Mobile 4G Dual, API 23) cannot query the UTM information. The code below lands on the case of InstallReferrerResponse.FEATURE_NOT_SUPPORTED. So I am almost sure that the install referrer client is bugged on some API levels.
Edit: Opened an issue to the Google: https://issuetracker.google.com/issues/149342702
As I don't know how you are processing the resonse, I can show you the way we did it in our implementation.
ReferrerDetails response = referrerClient.getInstallReferrer();
if (response == null) {
break;
}
String[] installReferrer = response.getInstallReferrer().split("&");
if (installReferrer.length >= 1) {
utmSource = installReferrer[0].split("=")[1];
}
if (installReferrer.length >= 2) {
utmMedium = installReferrer[1].split("=")[1];
}
Compare this snippet with yours and check if anything differs.

Android In App Billing with static id ‘android.test.cancelled’, responds with Payment Successful

I feel like I’m missing something really silly here!
After much confusion, reading and printing off seemingly endless contradictory information, I finally dived in and started testing in app billing. The activity is connecting ok to the Google Play server because it is returning the sku and price ok when calling getSkuDetails() in OnCreate().
I have set up product Id’s on the developer console, and also a test user. I uploaded an APK onto Alpha Testing a few days ago. On my device, signed in to Google Play Store with my developer account and ran the app in debug mode, setting android.test.cancelled as the product id in the launchPurchaseFlow () (in IabHelper).
The Google Payment dialog pops up, I click BUY and then unexpectedly get a Payment Successful message.
In the LogCat, the billing response code is 0 (BILLING_RESPONSE_RESULT_OK), causing handleActivityResult() to check the returned signature and purchasedata, which aren’t there ( makes sense? - because a purchase has not been requested in the first place). mPurchaseFinishedListener then treats this as an unknown error –1008.
public void onButtonClicked(View view) {
// Call BILLING API:
//
if (!billingStartedOk) {
Toast.makeText(context, "Purchase requires Google Play Store (billing) on your Android.", Toast.LENGTH_LONG).show();
return;
}
String payload = GameVariables.genPayLoad(); //
try {
mHelper.launchPurchaseFlow(activity, reservedProductIdCancelled, RC_REQUEST, mPurchaseFinishedListener, payload);
} catch (IabHelper.IabAsyncInProgressException e) {
e.printStackTrace();
}
}
D/GWL: in handleActivityResult, got responseCode = 0
D/GWL: in handleActivityResult, got resultCode = -1
E/IabHelper: In-app billing error: BUG: either purchaseData or dataSignature is null.
D/GWL: in handleActivityResult, got purchaseData == null || dataSignature == null
D/GWL: In mPurchaseFinishedListener - returned result.getResponse() = -1008
D/GWL: **** TrivialDrive Error: Error purchasing: IAB returned null purchaseData or dataSignature (response: -1008:Unknown error)
D/GWL: In mPurchaseFinishedListener - got !result.isSuccess

Google In-App Billing returning invalid signature for test purchases

I am having trouble with license testing my in-app products. I have an app published to the Google Play beta channel, and I have my relevant Google account listed as a license tester in the dev console. (Meaning I can make "purchases" without actually paying for the item(s).)
When I visit the in-app store for the first time, everything works fine. However, upon "purchasing" an item, I receive a signature verification error in the purchase flow response. From that point forward, I also receive the same signature error when querying for the store inventory.
I need help with this part. I have seen posts stating that the method verifyPurchase within Security.java is to blame. Those other posts state the issue is with android.test.purchased returning an empty String for the signature. What I'm seeing is different. My call passes the isEmpty(signature) test, but then is rejected later in the code. Why is Google In-App Billing returning a signature that is invalid? I have included the relevant code below.
public class Security {
// ...
// This method is part of the trace from
// IabHelper.queryInventory and IabHelper.onActivityResult
public static boolean verifyPurchase(String base64PublicKey, String signedData,
String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) { // Most say their problem is here
Log.e(TAG, "Purchase verification failed: missing data.");
return false;
}
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature); // My code gets here, but...
}
public static boolean verify(PublicKey publicKey, String signedData,
String signature) {
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig = initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) { // ...verify fails; return false
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
}
return false;
}
}
Update: As I mentioned in my comment, I was running a signed APK, with the debuggable flag set to true. I have now tried making an actual purchase, with a real credit card; I saw the exact same results. Even though the Google Play purchase flow completed as expected, I did not actually receive my product. Upon returning to the store, Google Play did not return the in-app inventory.
My problem actually had nothing to do with the Google Play In-App Billing Java code. I was reading the wrong license key for my app. My project has several different flavors. When this particular flavor was added, the file that contains the license key was copied from a different flavor and never changed. Now that I have corrected the key, everything is working as intended.
TL;DR: Verify that your Google Play license key is correct.

In App Billing Error: Item already owned (BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED)

I am working on Android Product which supports in app purchase and have large quantity of users doing in app purchases , out of all users some users are facing "BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED" error and the response returned is 7 .
I am calling consume call after every purchase being awarded , and also verifying my inventory when the InApp is setup on the start of application and check for any owned item and call the consume on it.
Any recommendation's to solve the issue .
See here. Google says that you MUST consume managed inapp items. If you don't, the user cannot purchase another one. After successful purchase, use:
int response = mService.consumePurchase(3, getPackageName(), token);
You get the token from the purchaseData JSON object:
final String token = jo.getString("purchaseToken");
But if you for some reason did not consume a purchase, you are stuck.
I ran into the same problem because I upgraded to iap api v3 and in v2 this was not a problem.
When you get this error, try to consume all purchases of the given productId. Or just consume everything purchased during setup of the service. Note that you might want to actually provision the purchase after consumePurchase() successfully returns depending on the semantics of your application.
Call the following code after the service is connected (in onServiceConnected()) and of course execute it in background:
String continuationToken="";
boolean hasMorePurchases=false;
do {
try {
Bundle purchases = mService.getPurchases(3, activity.getPackageName(), "inapp", continuationToken);
int response = purchases.getInt("RESPONSE_CODE");
if (response == 0) {
continuationToken = purchases.getString("INAPP_CONTINUATION_TOKEN");
if(!TextUtils.isEmpty(continuationToken)) hasMorePurchases=true;
final ArrayList<String> purchaseDataList = purchases.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
for(String purchaseJSON : purchaseDataList) {
JSONObject object = new JSONObject(purchaseJSON);
String productId = object.getString("productId");
String orderId = object.getString("orderId");
String purchaseToken = object.getString("purchaseToken");
Log.i(getClass().getSimpleName(),"consuming purchase of " + productId + ", orderId " + orderId);
mService.consumePurchase(3, activity.getPackageName(), purchaseToken);
}
} else {
Log.e(getClass().getSimpleName(), "could not get purchases: " + response);
}
} catch (RemoteException e) {
Log.e(getClass().getSimpleName(), "RemoteException during getPurchases:", e);
} catch (JSONException e) {
Log.e(getClass().getSimpleName(), "JSONException during getSkuDetails:", e);
}
} while(hasMorePurchases);
Note that each call to getPurchases returns a maximum of 700 purchases, so you need to use the continuation token to get more.
You can of course just use this code if you get the ALREADY_OWNED error code and only for the productId involved. Afterwards, try start the purchase again.

Categories

Resources