I'm making an Android application that will include a subscription using in-app billing from Google (https://developer.android.com/google/play/billing/index.html).
The aim is to let user to have 1 subscription per device.
I know that Google limit subscription for 1 google mail account but not for 1 device, that's why I made my own restriction using a server with a database.
So I made a pool of 10 subscriptions in the developer console product list and I want that when a device subscribe for the 1st subscription, a second device (using the same google account) will subscribe for the next subscription...
But when I want the second device chose automatically the next subscription not even bought on the account, it is saying to me "Product already own". The problem is that the current inventory is not refreshed.
I'm using IabHelper and here is the part of code where I'm trying to buy the next subscription available.
public void initIab() throws IabHelper.IabAsyncInProgressException {
iabHelper = new IabHelper(this, AppConfig.APPLICATION_KEY);
iabHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) throws IabHelper.IabAsyncInProgressException {
if (result.isSuccess()) {
iabHelper.queryInventoryAsync(iabInventoryListener());
billingServiceReady = true;
}
}
});
}
private IabHelper.QueryInventoryFinishedListener iabInventoryListener() {
return new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
if (iabHelper == null) {
return;
}
if (!result.isSuccess()) {
return;
}
String[] PREMIUM_KEYS = {"premium1","premium2","premium3","premium4","premium5","premium6","premium7","premium8","premium9","premium10"};
Purchase premiumPurchase = null;
String premiumKey;
boolean payload = false;
for(String key : PREMIUM_KEYS) {
if (inventory.hasPurchase(key)) {
premiumPurchase = inventory.getPurchase(key);
premiumKey = key;
payload = verifyDeveloperPayload(hasPurchase,premiumKey);
if(payload)
break;
}
}
session.setPremium(achatPremium != null && payload);
}
};
}
Please could you help me to find a solution ?
Sorry for my bad english.
Thank you.
I am making in-app purchases like TrivialDrive. Call queryInventoryAsync:
mHelper.queryInventoryAsync(mGotInventoryListener);
mGotInventoryListener:
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
String price = inventory.getSkuDetails(SKU_ALL_INCLUSILE).getPrice();
Log.d("SKU", "price " + price);
}
};
OnClick work perfectly and I can make purchase. But getPrice() method produces NPE. Help me please! I want to show SKU information in ListView.
SOLVED! Just use:
mHelper.queryInventoryAsync(true, skuList, mGotInventoryListener);
I am using API 3 for in app feature in my app.Currently for test purpose i am using test item code
android.test.purchased
Everything works fine i am able to buy product.But if i click "Purchase" button again, it says"already purchased" and need 30 minutes to let user buy again.Then i tried to consume this product using the following code but its ends up inside QueryInventoryFinishedListener's if(result.isFailure) condition.Following is my code for consuming
public void consumeItem() {
mHelper.queryInventoryAsync(mReceivedInventoryListener);
}
IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// Handle failure
Log.i("Ajji", "Failed in Query Inventory Finished Listener");
} else {
mHelper.consumeAsync(inventory.getPurchase(ITEM_SKU),
mConsumeFinishedListener);
}
}
};
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase, IabResult result) {
if (result.isSuccess()) {
// clickButton.setEnabled(true);
Log.i("Ajji", "Item Consumed Successfully");
rowPayment.setClickable(true);
} else {
// handle error
Log.i("Ajji", "Not Consumed");
}
}
};
I call this method for consuming item, Further i am using Trivial Drivesample project of google to make calls
I got the solution thank you.I needed to edit the security.java file from Trivial drive Project .I changed "return false" line in the verifyPurchase method to "return true" (dont forget its only for debug mode) .Now I can consume test in app successfully .Following is the edit of Verify Purchase method
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.");
if (BuildConfig.DEBUG) {
return true;
}
return false;
}
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature);
}
I need to add a subscription support to my application, so that the user can buy a subscription for a year of service.
I just have created the subscription on Google Developer Console.
My problem is: I don't have any idea how I can check the user subscription.
I'm working to do this:
When my app is started by the user, if there is network, the app contacts the Play Store and checks if the user has bought the subscription and the payment date. These data are always saved on locale file, so if there is no network the app will use the local data for checking;
If the user has bought the subscription I check if it's been over a year. In fact I have read on internet that Play Store provides only the payment date and not the finish subscription date;
If the checking is true the app will work in Premium mode and not in Standard mode;
Now the problems:
How do I check the purchase? Can I use hasPurchase() method like in normal in-app?
If I need to use hasPurchase() on point 1) does this method return False if the user don't renew the subscription after a year?
How can I know the purchase date?
I copy a piece of code, this is a valid code to check normal in-app and I'd like to edit it to use it in subscription checking:
private void checkForPremium() {
final IabHelper buyHelper = new IabHelper(this, "MYKEY");
// initialize the InApp Billing system
buyHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
#Override
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
buyHelper.dispose();
Log.e("MYAPP", "Error: " + result);
return;
}
// Get a list of all products the user owns
buyHelper.queryInventoryAsync(new IabHelper.QueryInventoryFinishedListener() {
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inv) {
if (result.isFailure()) {
buyHelper.dispose();
Log.e("MYAPP", "Error: " + result);
} else {
boolean isPremium = inv.hasPurchase("MYSKU");
inv.getSkuDetails().
buyHelper.dispose();
// Forward to the currect activity depending on premium / demo mode
if (isPremium) {
if(menu != null){
MenuItem item = menu.findItem(R.id.action_premium);
item.setVisible(false);
}
Log.w("MYAPP", "PREMIUM");
} else {
if(menu != null){
MenuItem item = menu.findItem(R.id.action_premium);
item.setVisible(true);
}
Log.w("MYAPP", "NO PREMIUM");
}
}
}
});
}
});
}
I am currently testing In-App Billing for a future app, and after I successfully "bought" the test item "android.test.purchased" the first time, I now receive the response code 7 every time I try to buy it again, which means that I already own this item.
12-15 23:02:14.149: E/IabHelper(19829): In-app billing error: Unable
to buy item, Error response: 7:Item Already Owned
From what I understand, this purchase is supposed to always be possible, right? So that the developer can test his/her app?
If not, how can I "reset" its state to not owned? I am using the util package from the Google In-App Billing Sample.
Add this code to a thread to initiate consume request.
int response = mService.consumePurchase(3, getPackageName(), purchaseToken);
Here for the purchase test, purchaseToken is
purchaseToken = "inapp:" + getPackageName() + ":android.test.purchased";
And
if (response == 0)
then the consumption is successful.
also don't forget to make mService public in
IabHelper.Java
then it would be possible to access like this:
int response = mHelper.mService.consumePurchase(3, getPackageName(), purchaseToken);
No need to write any special consumption code. Just use the adb command for clearing the Google Play Store data:
adb shell pm clear com.android.vending
It turns out that the android.test.purchased item behaves like a regular ID. It means that if you want be able to buy it again, you have to consume it somewhere in your code. I think that the Google documentation is misleading on this matter, and that they should add another static ID that you can buy endlessly for test purposes.
In-app version 3:
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
.....................
if (inventory.hasPurchase(SKU_CONTENT)) {
mHelper.consumeAsync(inventory.getPurchase(SKU_CONTENT), null);
}
}
};
Version 3 - Fastest way to solve : Clearing the cache of Google Play Store will let "android.test.purchased" available again.
This is how we can consume the Item
consume.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
String purchaseToken = "inapp:" + getPackageName() + ":android.test.purchased";
try {
Log.d("","Running");
int response = mService.consumePurchase(3, getPackageName(), purchaseToken);
if(response==0)
{
Log.d("Consumed","Consumed");
}else {
Log.d("","No"+response);
}
}catch (RemoteException e)
{
Log.d("Errorr",""+e);
}
}
});
t.start();
}
});
In my opinion if your program is not designed to consume the item you do not need to tweak the code in order to clear the memory of an outside vendor. This will make your code more fragile and you will have then to spend a lot of time to add and remove code that does not belong to your software so it is a bad design to implement a solution like that.
The best solution that worked for me to clear android.test.purchased was
adb uninstall com.yourapp.name
and then
adb shell pm clear com.android.vending
I did not need to clear cash and to browse my apps setting or to change code for that. I did need to add the adb to path variables of windows system which was pretty straight forward. So yes you need to use adb which you probably need anyway so..
You just add your C:\ ...\android-sdk\platform-tools; in windows path in environment variables, and I imagine that it is pretty simple in mac and linux os as well. Hope it helps someone to spend few days less with implementing android in app billings.
Go to the Google Play Developer Console, open Order Management menu item from the left side and select the order you want to refund. Also make sure to remove the entitlement.
The main issue is you have to consume the android.test.purchased item. But this item won't be available in your query inventory, so you can't consume using the normal flow.
So, if you are using IabHelper, in IabHelper class, you can temporarily change the IInAppBillingService mService to public so that it is accessible from your IabHelper.
Then in your class, you can consume like this,
int response = mHelper.mService.consumePurchase(3, getPackageName(), "inapp:"+getPackageName()+":android.test.purchased");
If success, the response is going to be 0.
Hope this helps.
For testing purposes I also suggest you to insert a piece of code that will be clearing all the products that you've bought before calling a method that initializes gp purchase flow. That is especially comfortable, when you test just one item at the moment. E.g. like this:
PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
for (Purchase sourcePurchase : purchasesResult.getPurchasesList()) {
if(sourcePurchase != null){
ConsumeResponseListener listener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(String outToken, #BillingResponse int responseCode) {
System.out.println("all consumed");
}
};
mBillingClient.consumeAsync(sourcePurchase.getPurchaseToken(), listener);
}else{
System.out.println("null");
}
}
// and then initiate whole process with clear "shoping basket"
BillingFlowParams.Builder builder = new BillingFlowParams.Builder()
.setSku(itemName).setType(BillingClient.SkuType.INAPP);
If you are in test environment
1) In the case of android.test.purchased, I can reset the fake payment by restarting android device(consumed the inventory).
2) In InApp util there is a file called Security.java make it as following, for temporary. Since the testing payment(fake) always return false due to security exception.
public static boolean verifyPurchase(String base64PublicKey,
String signedData, String signature) {
return true; }
Then in your OnIabPurchaseFinishedListener call fechInvForconsumeItem()
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
= new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result,
Purchase purchase)
{
if (result.isFailure()) {
// Handle error
Log.e("123","Failure");
return;
}
else if (purchase.getSku().equals(ITEM_SKU)) {
Log.e("123","PURCAsed");
fechInvForconsumeItem(); // Restart device if not consume
}
}
};
The fechInvForconsumeItem() is
public void fechInvForconsumeItem() {
mHelper.queryInventoryAsync(mReceivedInventoryListener);
}
IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener
= new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// Handle failure
Log.e("11","Failure");
} else {
Log.e("11","suc");
mHelper.consumeAsync(inventory.getPurchase(ITEM_SKU),
mConsumeFinishedListener);
}
}
};
Consume Listener is
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener =
new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase,
IabResult result) {
if (result.isSuccess()) {
} else {
// handle error
Log.e("11","sucConsume");
}
}
};
IabHelper.QueryInventoryFinishedListener
mQueryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory)
{
if (result.isFailure()) {
return;
}
try {
if(inventory.hasPurchase("product_sku_id"))
{
isItemEnable= true;
mHelper.consumeAsync(inventory.getPurchase("product_sku_id"),null);
}
else
{
isItemEnable = false;
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
In my case, it appears that Google does not record a purchase for the item. Rather, the local copy of Google Play Services caches the purchase. That way, when a second request is made on the same device, android.test.purchased already owned appears. However, using another device or resetting the device clears the cache, and allows the purchase to be repeated.
In my case, I just needed to clear the apps cache. After clearing the cache, I was able to initiate the purchase flow again.
From my device (4.4.2), I navigated to "Settings->Application manager". Next, I selected the app from the "DOWNLOADED" tab, and then "Clear cache".
This is the difference between consumable and non-consumable items; non-consumable items (what you seem to be dealing with here) have their state tracked persistently, while consumable items can be purchased multiple times. You'll have to go into your Play management console and cancel/refund the sale to test it again.