I have kept a donate tab and want to let the users buy the items over and over again. I have implemented a code but it lets the user buy the specific item only once. I have used managed products in play console for products.
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(billingClient.isReady()){
SkuDetailsParams params=SkuDetailsParams.newBuilder()
.setSkusList(Arrays.asList("purchase_aaa","purchase_bbb","purchase_ccc","purchase_ddd"))
.setType(BillingClient.SkuType.INAPP).build();
billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
if(responseCode==BillingClient.BillingResponse.OK)
{
loadProductToRecyclerView(skuDetailsList);
}
else{
Toast.makeText(Donate.this, "Cannot query product", Toast.LENGTH_SHORT).show();
}
}
});
}
else
{
Toast.makeText(Donate.this, "Not ready", Toast.LENGTH_SHORT).show();
}
}
});
#Override
public void onPurchasesUpdated(int responseCode, #Nullable List<Purchase> purchases) {
if(purchases!=null){
Toast.makeText(this, "Purchased"+purchases.size(), Toast.LENGTH_SHORT).show();
}
}
That's by design and cannot be changed, in-app managed products can only be purchased once.
If you want the user who has paid more to have more features enabled, you will have to create as many in-app managed products as levels exist.
If it is a game in which, for example, the user is consuming items then when he no longer has any, you consume the in-app product so he can buy it again.
Or you can also consume the product immediately after the purchase and keep track of how many he has purchased through your own means, an own server or perhaps through firebase, but this already means that you will have to implement a user authentication system for your app.
Consume a purchase:
ConsumeResponseListener consumeListener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
}
};
String token = purchase.getPurchaseToken();
ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(token).build();
billingClient.consumeAsync(consumeParams, consumeListener);
Related
I am implementing IAP in my app. One is for removing ad and other is for adding more puzzle. I was testing that on my device but came across an issue. After buying an item, I am getting response code "Item already owned" but it is not showing in purchase list.
I am setting up my billing client like this,
private void setUpBillingClient(){
mBillingClient = BillingClient.newBuilder(this).setListener(this).build();
mBillingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponseCode) {
if (billingResponseCode == BillingClient.BillingResponse.OK) {
List skuList = new ArrayList<>();
skuList.add(ITEM_SKU_MORE_PUZZLE);
skuList.add(ITEM_SKU_REMOVE_AD);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
mBillingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List skuDetailsList) {
// Process the result.
if (responseCode == BillingClient.BillingResponse.OK
&& skuDetailsList != null) {
for (Object skuDetailsObject : skuDetailsList) {
SkuDetails skuDetails = (SkuDetails) skuDetailsObject;
String sku = skuDetails.getSku();
String price = skuDetails.getPrice();
if (ITEM_SKU_MORE_PUZZLE.equals(sku)) {
btnMorePuzzle.setText(price);
}
else if(ITEM_SKU_REMOVE_AD.equals(sku)) {
btnRemoveAd.setText(price);
}
}
}
}
});
}
}
#Override
public void onBillingServiceDisconnected() {
//Toast.makeText(getApplicationContext(), getResources().getString(R.string.billing_connection_failure), Toast.LENGTH_SHORT);
}
});
queryPurchases();
queryPrefPurchases();
}
First question, why is Billing response is OK here when I have already purchased the item. I don't want to set text of button as price, which is getting set from this response after product is bought.
This is my Onpurchase implementation,
#Override
public void onPurchasesUpdated(int responseCode, #Nullable List<Purchase> purchases) {
if (responseCode == BillingClient.BillingResponse.OK && purchases != null) {
for (Purchase purchase : purchases) {
if (purchase.getSku().equals(ITEM_SKU_REMOVE_AD)) {
mSharedPreferences.edit().putBoolean("ad_free", true).commit();
btnRemoveAd.setText("Done");
btnRemoveAd.setEnabled(false);
}
else if(purchase.getSku().equals(ITEM_SKU_MORE_PUZZLE)){
mSharedPreferences.edit().putBoolean("more_puzzle", true).commit();
btnMorePuzzle.setText("Done");
btnMorePuzzle.setEnabled(false);
}
}
} else if (responseCode == BillingClient.BillingResponse.ITEM_ALREADY_OWNED ) {
// I am getting response "Item already owned" here for item bought but purchase list here is empty
// so i can't do anything for purchased item
}
}
Second question, here I am getting response that my item is already bought but still list is empty.
How to implement it properly?
If someone already bought a product then button should be disabled.
Another doubt is while testing do I have to wait for 1-2 hrs to get that item refunded from playstore to test again or is there any other method.
I am following this code for in-app implementation.
https://github.com/patpatchpatrick/Streakr/
There is only one thing to point out here, that you are querying only the SKU type INAPP items only not the SUBS. I think the type of product you are providing comes under subscriptions not under in-app products which are used to be consumed. That is why your query is empty.
I found a mistake in your onPurchasesUpdated, conditions are set wrongly.
It should go something like this,
if(BillingResponse.OK && purchases != null) {
// update records
} else if(BillingResponse.ITEM_NOT_OWNED){ //this condition was missing
// update records if required or ask to buy
} else if(BillingResponse.ITEM_ALREADY_OWNED ){ // update records}
and i also suggest to update db for this.
I am developing an application which will allow user to purchase using In App Purchase and I want to remove ads after purchase. I can purchase succesfully with code below
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSku("android.test.purchased")
.setType(BillingClient.SkuType.INAPP)
.build();
mBillingClient.launchBillingFlow(getActivity(), flowParams);
But I cannot see the result from queryPurchaseHistoryAsync when I open app again and call this method below.
mBillingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP, new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(int responseCode, List<Purchase> purchasesList) {
purchasesList.size();
}
});
purchasesList.size() == 0
Is "queryPurchaseHistoryAsync" method cannot show test purchase or Am I doing something wrong?
Edit: Is queryPurchaseHistoryAsync method check purchase after delete and install app again.
Yes queryPurchaseHistoryAsync method check purchase after deleting and installing the app again against particular user
mBillingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP, new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(#NonNull BillingResult billingResult, #Nullable List<PurchaseHistoryRecord> list) {
}
});
Try this it will give all purchase items.
mBillingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
listener.onSkuDetailsResponse(responseCode, skuDetailsList);
}
});
mBillingClient.queryPurchases() is all you need. Call it at every app start and, for example, every time your main activity resumes. This way your (reinstalled) app will eventually detect all user's purchases.
QueryInventoryFinishedListener of IabHelper has not returned the expired subscription items.
On the other hand, PurchaseHistoryResponseListener of Google Play Billing Library seems to receive all purchased items, which is including expired items.
On Google Play Billing Library, we have to check the purchased date of PurchaseHistoryResponseListener and each expiration date of items?
queryPurchases vs queryPurchaseHistoryAsync
Generally, we should use queryPurchases(String skuType), which does not returns expired items. queryPurchaseHistoryAsync returns enabled and disabled items, as you see the documentation like following.
queryPurchases
Get purchases details for all the items bought within your app. This method uses a cache of Google Play Store app without initiating a network request.
queryPurchaseHistoryAsync
Returns the most recent purchase made by the user for each SKU, even if that purchase is expired, canceled, or consumed.
About queryPurchaseHistoryAsync
I could not image the use case for queryPurchaseHistoryAsync. If we need to use queryPurchaseHistoryAsync, we need the implementation to check if it is expired or not.
private PurchaseHistoryResponseListener listener = new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(int responseCode, List<Purchase> purchasesList) {
for (Purchase purchase : purchasesList) {
if (purchase.getSku().equals("sku_id")) {
long purchaseTime = purchase.getPurchaseTime();
// boolean expired = purchaseTime + period < now
}
}
}
};
Purchase object does not have the information of period, so the above period must be acquired from BillingClient.querySkuDetailsAsync or be hard-coded. The following is sample implementation to use querySkuDetailsAsync.
List<String> skuList = new ArrayList<>();
skuList.add("sku_id");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS);
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
if (skuDetailsList == null) {
return;
}
for (SkuDetails skuDetail : skuDetailsList) {
if (skuDetail.getSku().equals("sku_id")) {
String period = skuDetail.getSubscriptionPeriod();
}
}
}
});
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 have implemented in-app purchased in my application and my product type is Managed and i am using API version 3.
When i make purchase from my credit card it is successfully done.
But the problem is if i uninstall my application and want to purchase this with same account it will charge me again?
According to Google rules of managed product type we only purchase the product once? But why is this happening ?
any one help me please?
here is my PurchaseActivity.java class
public abstract class PurchaseActivity extends BlundellActivity implements OnIabSetupFinishedListener, OnIabPurchaseFinishedListener {
private IabHelper billingHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_purchase);
setResult(RESULT_CANCELED);
billingHelper = new IabHelper(this, AppProperties.BASE_64_KEY);
billingHelper.startSetup(this);
}
#Override
public void onIabSetupFinished(IabResult result) {
if (result.isSuccess()) {
Log.d("In-app Billing set up" + result);
dealWithIabSetupSuccess();
} else {
Log.d("Problem setting up In-app Billing: " + result);
dealWithIabSetupFailure();
}
}
protected abstract void dealWithIabSetupSuccess();
protected abstract void dealWithIabSetupFailure();
protected void purchaseItem(String sku) {
billingHelper.launchPurchaseFlow(this, sku, 123, this);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
billingHelper.handleActivityResult(requestCode, resultCode, data);
}
*/
#Override
public void onIabPurchaseFinished(IabResult result, Purchase info) {
if (result.isFailure()) {
dealWithPurchaseFailed(result);
} else if (pmg.SKU.equals(info.getSku())) {
dealWithPurchaseSuccess(result, info);
}
finish();
}
protected void dealWithPurchaseFailed(IabResult result) {
Log.d("Error purchasing: " + result);
}
protected void dealWithPurchaseSuccess(IabResult result, Purchase info) {
Log.d("Item purchased: " + result);
// DEBUG XXX
// We consume the item straight away so we can test multiple purchases
billingHelper.consumeAsync(info, null);
// END DEBUG
}
#Override
protected void onDestroy() {
disposeBillingHelper();
super.onDestroy();
}
private void disposeBillingHelper() {
if (billingHelper != null) {
billingHelper.dispose();
}
billingHelper = null;
}
}
This is working as intended - in your code you are consuming the in-app purchase immediately, which means you can then purchase it again:
protected void dealWithPurchaseSuccess(IabResult result, Purchase info) {
Log.d("Item purchased: " + result);
// DEBUG XXX
// We consume the item straight away so we can test multiple purchases
billingHelper.consumeAsync(info, null);
// END DEBUG
}
There's nothing that says you can't purchase a managed product more than once. What you can't do is purchase a managed product before a previous purchase of the same managed item has been consumed. So this is working exactly as intended, and if you remove that call to consumeAsync, you'll see that you can't purchase it again.
Sample use case:
Imagine some game where you can purchase extra lives. First, the user would purchase the extra lives (a managed in app product), your game (client or server) would then add those lives to the user's profile, for example, and assuming that was successful, you'd tell Google Play that the purchase has been consumed.
This is important in order to handle error cases - for example say the user's device dies in between the initial purchase and the addition of lives to the user's profile. Your app can then, the next time it's launched, try again to add those lives, and consume the purchase on success. And, obviously you wouldn't want the user trying to purchase even more lives before you successfully grant them - which is why you can't purchase a managed product twice before it's been consumed.