I have the serverless android app with simple functional: if user has some in-app subscription (auto renewable), then he can use functional in app, otherwise there is no. I know, how to make functional with obtaining subscriptions info (price, title etc) and calling payment. But I can not check if current user has active (not cancelled) subscriptions. I read so many information on many sites and tutorials, and there was written that I must use google API in my server. But I do not have my own server.
I used two different libraries for in-app subscriptions:
'com.anjlab.android.iab.v3:library:1.0.44'
and
'com.android.billingclient:billing:1.1'
but no one helped me for checking if user has active subscriptions. So, how to make this task? Help me please, maybe I missed some information...
Edit: Anjlab library hasn't been updated in the longest time ever. Kindly use Google's own billing library, this step-by-step process should help you easily integrate it into your app - Google In App Billing library
Using the anjlab in-app-billing library I was also facing the similar. This is what I did to get around it.
Invoke the method billingProcessor.loadOwnedPurchasesFromGoogle(); Then check the value of transactionDetails, if the TransactionDetails object return null it means, that the user did not subscribe or cancelled their subscription, otherwise they are still subscribed.
void checkIfUserIsSusbcribed(){
Boolean purchaseResult = billingProcessor.loadOwnedPurchasesFromGoogle();
if(purchaseResult){
TransactionDetails subscriptionTransactionDetails = billingProcessor.getSubscriptionTransactionDetails(YOUR_SUBSCRIPTION_ID);
if(subscriptionTransactionDetails!=null)
//User is still subscribed
else
//Not subscribed
}
}
Also, point to note is that the TransactionDetails object will only return null after the period of the subscription has expired.
Have you try to call bp.loadOwnedPurchasesFromGoogle(); ?
Edit
So try this :
Purchase purchase = inventory.getPurchase(product);
Log.d(TAG, "Purchase state: " + purchase.getPurchaseState());
// 0 (purchased), 1 (canceled), or 2 (refunded).
if (purchase.getPurchaseState() == 0
|| purchase.getPurchaseState() == 2) {
showPremiumVersion();
} else {
showFreeVersion();
}
Or this solution :
bp.isPurchased("yourSKU")
The isPurchsed method can't catch history of error's purchase / canceled Purchase/ Retrived Purchase.
Use this and don't forget to add INTERNET permission in your manifest.
TransactionDetails transactionDetails = billingProcessor.getPurchaseTransactionDetails("productId");
if (transactionDetails != null) {
//Already purchased
//You may save this as boolean in the SharedPreferences.
}
Related
I use Google Play in-app in my app based the official sample project.
The Code A is to handle non-consumable products, it works well when I launch it using com.android.billingclient:billing-ktx:3.0.3 .
After I upgrade the project from Google Play Billing Library 3 to 4, I find the code purchase.sku doesn't work, so I have to replace it with purchase.skus.
The code of purchase.skus can be compiled in com.android.billingclient:billing-ktx:4.0.0, but I can't get the correct order, the test purchase is refunded after 3 minutes, it seems that Google Play doesn't acknowledge the purchase.
How can I fix the Code A when I upgrade Google Play Billing Library 3 to 4 ?
Code A
private fun processPurchases(purchasesResult: Set<Purchase>) {
val validPurchases = HashSet<Purchase>(purchasesResult.size)
purchasesResult.forEach { purchase ->
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (purchase.sku.equals(purchaseItem)) {
//if (purchase.skus.equals(purchaseItem)) { //sku -> skus in 4.0
if (isSignatureValid(purchase)) {
validPurchases.add(purchase)
}
}
} else if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {
Log.d(LOG_TAG, "Received a pending purchase of SKU: ${purchase.sku}")
// handle pending purchases, e.g. confirm with users about the pending
// purchases, prompt them to complete it, etc.
mContext.toast(R.string.msgOrderPending)
} else {
mContext.toast(R.string.msgOrderError)
}
}
acknowledgeNonConsumablePurchasesAsync(validPurchases.toList())
}
I'm not sure what is the exact reason of changing this method but I think that's probably because of the new subscriptions model in Google Play. Moreover this new method gets deprecated in the 5.0.0 version.
However, since the purchase.skus became a List of Strings, you could just check for your purchaseItem inside it. I think it depends on your purchases setup. Assuming your purchaseItem is also a String, you could either find your purchaseItem there:
if (purchase.skus.any { it == purchaseItem })
or simply take the first one to compare:
if (purchase.skus[0] == purchaseItem)
Of course, you should debug it to check what's exactly inside the skus list and then choose the best way to fix that.
I'm having trouble figuring out how to detect when a refund has been issued for a managed (uncomsumable) in-app product in Android using com.android.billingclient:billing:2.0.3. The problem seems fairly deep though maybe I'm making it more complicated than it ought to be.
To begin, I've made a test purchase which has been acknowledged AND refunded:
Looking at the logs of my app I see the following:
D/BillingManager: Got a verified purchase: Purchase. Json: {"orderId":"GPA.3362-7185-5389-78416","packageName":"com.glan.input","productId":"pro","purchaseTime":1567672759460,"purchaseState":0,"purchaseToken":"pbkpcaadklleoecegjfjdpbl.AO-J1OwsR6WVaVZCCYOU6JyYN1r0qJsrwitIPZfhc3jX4yketRUwNzKqwMgYx0TgZ2GebEGbXDL0RlMyogwtSKSPsaHCJ4RA4MPlIGay-aM1-QhmnqwjXjQ","acknowledged":true}
I/BillingManager: purchase pbkpcaadklleoecegjfjdpbl.AO-J1OwsR6WVaVZCCYOU6JyYN1r0qJsrwitIPZfhc3jX4yketRUwNzKqwMgYx0TgZ2GebEGbXDL0RlMyogwtSKSPsaHCJ4RA4MPlIGay-aM1-QhmnqwjXjQ is in 1 state
There's something funny going on here:
We can see the order IDs match up between what's in the image and the detected purchase
The first log line is printing the purchase with Log.d(TAG, "Got a verified purchase: " + purchase); which is printing the underlying JSON which represents the purchase.
Note that "purchaseState":0
The second log line is issued with Log.i(TAG, "purchase " + purchase.getPurchaseToken() + " is in " + purchase.getPurchaseState() + " state");.
Note that here purchase.getPurchaseState() is resulting in a value of 1
If I look at the implementation of getPurchaseState in Android Studio I see the following:
public #PurchaseState int getPurchaseState() {
switch (mParsedJson.optInt("purchaseState", PurchaseState.PURCHASED)) {
case 4:
return PurchaseState.PENDING;
default:
return PurchaseState.PURCHASED;
}
}
Earlier in the file the PurchaseState interface is declared as:
#Retention(SOURCE)
public #interface PurchaseState {
// Purchase with unknown state.
int UNSPECIFIED_STATE = 0;
// Purchase is completed.
int PURCHASED = 1;
// Purchase is waiting for payment completion.
int PENDING = 2;
}
It seems like getPurchaseState never returns PurchaseState.UNSPECIFIED_STATE and only returns PENDING which the JSON comes with a value of 4. I've confirmed that a state of PENDING is correctly returned when the purchase is performed with a payment method that takes a while to approve.
I've found posts like In-App Billing v3 - Don't detect refund which suggest that Play Services are caching purchases but I'm not convinced that's causing this problem because if I modify my code betweens runs of my app to acknowledge/consume the purchase those get state changes get immediately reflected in the JSON of the purchase.
How am I supposed to detect a refunded managed product?
I have one purchase (SkuType.INAPP) in my application. I make a test purchase and then make a refund.
Problem:
purchase.getOriginalJson() // contains "purchaseState":0
purchase.getPurchaseState() // returns 1
Inside com.android.billingclient.api.Purchase:
public int getPurchaseState() {
switch(this.zzc.optInt("purchaseState", 1)) {
case 4:
return 2;
default:
return 1;
}
}
//...
public #interface PurchaseState {
int UNSPECIFIED_STATE = 0;
int PURCHASED = 1;
int PENDING = 2;
}
Hacky way to check purchaseState from original json:
purchase.getOriginalJson().contains(String.format("\"purchaseState\":%s", Purchase.PurchaseState.PURCHASED))
Unfortunately, this problem still exists!
More details here.
You can check if still purchase exits following
binding.btnRestore.setOnClickListener(v->{
Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
for ( Purchase purchase : purchasesResult.getPurchasesList()){
handlePurchase(purchase);
}
});
Google Play returns the purchases made by the user account logged in to the device. If the request is successful, the Play Billing Library stores the query results in a List of Purchase objects.
Note: Only active subscriptions appear on this list. As long as the in-app product is on this list, the user should have access to it. For further information, see the Handle SUBSCRPTION_ON_HOLD section of Add subscription-specific features.
To retrieve the list, call getPurchasesList() on the PurchasesResult. You can then call a variety of methods on the Purchase object to view relevant information about the item, such as its purchase state or time. To view the types of product detail information that are available, see the list of methods in the Purchase class.
You should call queryPurchases() at least twice in your code:
Call queryPurchases() every time your app launches so that you can restore any purchases that a user has made since the app last stopped.
Call queryPurchases() in your onResume() method, because a user can make a purchase when your app is in the background (for example, redeeming a promo code in the Google Play Store app).
Calling queryPurchases() on startup and resume guarantees that your app finds out about all purchases and redemptions the user may have made while the app wasn't running. Furthermore, if a user makes a purchase while the app is running and your app misses it for any reason, your app still finds out about the purchase the next time the activity resumes and calls queryPurchases().
Query most recent purchases
The queryPurchases() method uses a cache of the Google Play Store app without initiating a network request. If you need to check the most recent purchase made by the user for each product ID, you can use queryPurchaseHistoryAsync(), passing the purchase type and a PurchaseHistoryResponseListener to handle the query result.
queryPurchaseHistoryAsync() returns a PurchaseHistory object that contains info about the most recent purchase made by the user for each product ID, even if that purchase is expired, cancelled, or consumed. Use queryPurchases() whenever possible, as it uses the local cache, instead of queryPurchaseHistoryAsync(). If using queryPurchaseHistoryAsync(), you can also combine it with a Refresh button, allowing users to update their list of purchases.
The following code demonstrates how you can override the onPurchaseHistoryResponse() method:
private void handlePurchase(Purchase purchase) {
if(purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (purchase.getSku().equals(skuPro)) {
EntityPRO entityPRO = RoomDB.getDatabase(context).proDAO().getLastItem();
entityPRO.isBought = true;
RoomDB.getDatabase(context).proDAO().updateSpecificSLI(entityPRO);
Toast.makeText(context, context.getString(R.string.pro_succesfully_bought), Toast.LENGTH_LONG).show();
}
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
}
}
}
I implemented android in app billing, uploaded it in developer console for alpha tests, created an item (one time product for premium version) and tested on my device. Everything worked, but I wanted to test a second time with same device - isn't it possible to undo purchase?
What I tried:
I canceled the purchased item in developer console --> nothing happened on my device, BillingClient.getPurchaseList still returned my premium purchase
I cleared cache with ad command "adb shell pm clear com.android.vending" --> and now it's getting strange:
First, I thought it works and BillingClient.queryPurchases().getPurchaseList didn't return any purchase and my app behaviour changed correctly to basic version. But if I try to buy the item one more time to test the purchase flow again, it says "item already owned". Isn't there any possibility testing it again??
Another strange thing I absolutely don't understand: I didn't do anything, openend my app a few hours later again and it is marked as premium again. What does that mean? Is in a problem in test account or can that also happen in real (canceled) purchases??
Thanks a lot for your help!
If you want to allow an item to be purchased multiple times (i.e in-game currency), you should consume it before buying it again, otherwise the IAB library will return the "Item already owned" error.
To reset a purchase you can use BillingClient#consumeAsync(String purchaseToken).
To get the purchaseToken of a purchase, use BillingClient#queryPurchaseHistoryAsync, this will return the list of current purchases.
If you want to consume all purchases for debugging purposes, you can just use the following code:
client.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP, new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(int responseCode, List<Purchase> purchasesList) {
if (purchasesList != null && !purchasesList.isEmpty()) {
for (Purchase purchase : purchasesList) {
client.consumeAsync(purchase.getPurchaseToken(), new ConsumeResponseListener() {
#Override
public void onConsumeResponse(int responseCode, String purchaseToken) {
if (responseCode == BillingResponse.OK) {
//Item consumed, you may repurchase it now
} else {
// Error, item not consumed. See responseCode for more info
}
}
});
}
}
}
});
Trying to clear cache won't fix the problem because as soon as the IAP library resyncs with GPlay, it will remember the purchases associated with the account of the current user.
In my application I offer the user to make a donation using Google Play IAP, in return I remove ads and unlock premium features.
When my application loads I want to check if user made a donation, how to do that via code knowing that after user makes a donation I'll call the following code to allow the user to make future donations if desired.
So, I want to allow the user to make further donations if desired, but I want to know if (s)he already made a donation to disable ads and unlock premium features.
BillingProcessor bp;
bp.consumePurchase(productId);
Note, my questions is about IAP online process not about saving a value offline and check it later.
I think this guide should help show you how to do this:
https://developer.android.com/google/play/billing/billing_library_overview
Query cached purchases
To retrieve information about purchases that a
user makes from your app, call the queryPurchases() method with the
purchase type (SkuType.INAPP or SkuType.SUBS) on the Play Billing
Library client. For example:
PurchasesResult purchasesResult = mBillingClient.queryPurchases(SkuType.INAPP);
Google Play returns the
purchases made by the user account logged in to the device. If the
request is successful, the Play Billing Library stores the query
results in a List of Purchase objects.
Note: Only active subscriptions appear on this list. As long as the
in-app product is on this list, the user should have access to it. For
further information, refer to Handle SUBSCRPTION_ON_HOLD section of
the Add subscription-specific features document. To retrieve the list,
call the getPurchasesList() method on the PurchasesResult object. You
can then call a variety of methods on the Purchase object to view
relevant information about the item, such as its purchase state or
time. To view the types of product detail information that are
available, see the list of methods in the Purchase class.
Call queryPurchases() at least twice in your code:
Every time your app launches so that you can restore any purchases
that a user has made since the app last stopped. In your onResume()
method because a user can make a purchase when your app is in the
background (for example, redeeming a promo code in Play Store app).
Calling queryPurchases() on startup and resume guarantees that your
app finds out about all purchases and redemptions the user may have
made while the app wasn't running. Furthermore, if a user makes a
purchase while the app is running and your app misses it for any
reason, your app still finds out about the purchase the next time the
activity resumes and calls queryPurchases().
Query most recent purchases
The queryPurchases() method uses a cache
of the Google Play Store app without initiating a network request. If
you need to check the most recent purchase made by the user for each
product ID, you can use the queryPurchaseHistoryAsync() method and
pass the purchase type and a PurchaseHistoryResponseListener to handle
the query result.
queryPurchaseHistoryAsync() returns the most recent purchase made by
the user for each product ID, even if that purchase is expired,
cancelled, or consumed. Use the queryPurchases() method whenever
possible, as it uses the local cache, instead of the
queryPurchaseHistoryAsync() method. You could combine
queryPurchaseHistoryAsync() with a Refresh button allowing users to
update their list of purchases.
The following code demonstrates how you can override the
onPurchaseHistoryResponse() method:
mBillingClient.queryPurchaseHistoryAsync(SkuType.INAPP,
new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(#BillingResponse int responseCode,
List purchasesList) {
if (responseCode == BillingResponse.OK
&& purchasesList != null) {
for (Purchase purchase : purchasesList) {
// Process the result.
}
}
} });
You can use this:
Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS); //Or SkuType.INAPP
if (purchasesResult.getPurchasesList() != null) {
for (Purchase purchase : purchasesResult.getPurchasesList()) {
if (purchase.getSku().equals("your_product_id")) handlePurchase(purchase);
}
[...]
void handlePurchase(Purchase purchase) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
premium = true; //In casse purchase was acknowledge before
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = new AcknowledgePurchaseResponseListener() {
#Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
premium = true;
}
};
billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
}
}
}
If you have any questions, go ahead and comment.
As nasch and AlexBSC already answered, you have to fetch for possible made purchases.
However, the most up to date method of doing this is calling BillingClient.queryPurchasesAsync() as described in here. You should at least call it in onResume() and onCreate().
for example like this,
billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS, new PurchasesResponseListener() {
#Override
public void onQueryPurchasesResponse(BillingResult billingResult, List<Purchase> purchases) {
if (billingResult.getResponseCode() == OK
&& purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
}
}
});
Following these steps should get you pretty far.
I'm very confused on how in-app billing works. I've read the documentation and I must have missed something because I don't understand the final step I need to implement into my application to make this work. The in-app billing works great, however, if a user uninstalls my app and installs it again at a future date, my application doesn't know how to determine if the in-app purchase has previously been made. Here's a snippet from my main class where I attempt to handle all of this:
#Override
public void onCreate(Bundle savedInstanceState)
{
mContext = this;
startService(new Intent(mContext, BillingService.class));
BillingHelper.setCompletedHandler(mTransactionHandler);
}
I am using the example classes from the dungeons example project. What I don't understand is how the below code works at the time of purchase, but re-running it doesn't work to check if something been purchased already. I have been stuck on this part for about a month now and I've been getting very frustrated with it.
public Handler mTransactionHandler = new Handler()
{
public void handleMessage(android.os.Message msg)
{
Log.i(TAG, "Transaction complete");
Log.i(TAG, "Transaction status: "
+ BillingHelper.latestPurchase.purchaseState);
Log.i(TAG, "Item purchased is: "
+ BillingHelper.latestPurchase.productId);
if (BillingHelper.latestPurchase.isPurchased())
{
new Message("Thank you!", getApplicationContext());
PAY_VERSION = true;
SharedPreferences purchasePreferences = getSharedPreferences(PURCHASE_PREFERENCES, Activity.MODE_PRIVATE);
Editor purchaseEditor = purchasePreferences.edit();
purchaseEditor.putBoolean("purchased", PAY_VERSION);
purchaseEditor.commit();
Intent intent = getIntent();
finish();
startActivity(intent);
}
};
What I need is some way to query the server to see if this item has been purchased or not. I understand that there's a PURCHASE_STATE_CHANGED thing there somewhere however I have no idea how to do anything when it determines the state has changed or how to initiate it to check. I'm lost and all I need is a good push in the right direction because so far I'm just completely lost.
EDIT:
I've also heard you need to parse JSON, but I have no idea how to even begin doing that.
EDIT 2:
Am I supposed to call this stuff to check?
BillingHelper.restoreTransactionInformation(BillingSecurity.generateNonce());
BillingHelper.getPurchaseInformation(new String[] {"myItem"});
That code previously had crashed on my sisters phone (SGS3, ICS) but not on mine (GN, ICS, and JB work). I was calling it in onCreate() of my first activity. Not really sure what to do with getPurchaseInformation(...) once it's been called. It has no return value so I'm not sure if I can parse the JSON or whatever I'm supposed to do...
Also, those 2 lines give me this:
08-27 11:54:04.271: E/BillingService(17702): BillingHelper not fully instantiated
08-27 11:54:04.271: E/BillingService(17702): BillingHelper not fully instantiated
JSON
An example of a JSON order object that includes a subscription purchase token is shown below.
{ "nonce" : 1836535032137741465,
"orders" :
[{ "notificationId" : "android.test.purchased",
"orderId" : "transactionId.android.test.purchased",
"packageName" : "com.example.dungeons",
"productId" : "android.test.purchased",
"developerPayload" : "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
"purchaseTime" : 1290114783411,
"purchaseState" : 0,
"purchaseToken" : "rojeslcdyyiapnqcynkjyyjh" }]
}
How to parse JSON in Java.
Android supports JSON library
Android Billing
If you have a remote server, we recommend that you store purchase information on your server instead of in a local database on a device. Look here.
Read about Managed per user account, Unmanaged, or Subscription
I use billing like this:
In onCreate
BillingHelper.setCompletedHandler(handlerTransaction);
You have handler in your code.
Next in onClick or something
BillingHelper.requestPurchase(this, currentMarketProduct.getMarketId());
You are on the right track with the second update. The trick is to realise everything is done in async.
The typical flow is as follows:
User installs your app.
On first load of your app, you check if you need to restore
purchases.
If you do, send a RESTORE_TRANSACTION synchronous request to Google.
Google will respond with a acknowlegment response to your
RESTORE_TRANSACTION request. (This is only an acknowlegement that
they received your request.)
At this point, you should mark that you had already sent a restore request to Google and no further restores needs to be sent from the app.
Now asynchronously Google will start sending a 'PURCHASE_STATE_CHANGED' event to your app for each in-app purchase the user has previously purchased. This call is the same as what Google would had sent if the user had made that purchase for the first time.
Since it's the same call, your app would pick up the event and handled it normally as if the user has just purchased the in-app product (thereby "restoring" the purchased feature).
In regard to steps 2 and 5, what I've done for my app is to keep a SharedPreference value called 'APP_INITIALISED' that defaults to false. Everytime my app starts up, if 'APP_INITIALISED' is false, I tell Google to RESTORE_TRANSACTION (step 2) then I set APP_INITIALISED to true (step 5).