Get specific offer from Purchase object Play Billing v5 - android

How to identify the specific subscription offer from Purchase object in new Google Play Billing v5 library? I can get the product id but there seems to be now way get offer id or token? In that case how do I notify users if they wish to upgrade/downgrade plans?
#Override
public void onPurchasesUpdated(#NonNull BillingResult billingResult,
#Nullable #org.jetbrains.annotations.Nullable List<Purchase> purchases) {
Purchase purchase=purchases.get(0);
//get the exact subscription offer related to this purchase
}

I think when you call launchBillingFlow the purchase dialog will show all the information, from which current plan to which new plan and how will Google charge the fee (ReplaceProrationMode).
But I think the Purchase object should contain the offer or base plan id/tag, imagine the case that I want to disable the base plan that the user is subscribing to prevent the user choose it again (of course purchase dialog will show already subscribed error).

Related

How to stop Google Play Billing from charging until an api request confirms the purchase can be made

I have a question about the Google Play Billing Library (version 5) but I guess older versions are fine too.
In Google Play Billing, the purchase flow is launched like below:
BillingResult billingResult = billingClient.launchBillingFlow(getActivity(), billlingFlowParams);
A listener waits for the purchase response, like below:
PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
#Override
public void onPurchasesUpdated(#NonNull #NotNull BillingResult billingResult, #Nullable #org.jetbrains.annotations.Nullable List<com.android.billingclient.api.Purchase> list) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {
....
....
Additional code here that makes an api request based on the Purchase Object
}
};
The problem is that as soon as we confirm the purchase Google charges for the purchase but I have a backend that also process the purchase, ie confirm the purchase is applicable etc... As a result what can happen is that someone confirms the purchase, Google charges the person but my backend goes, the purchase cannot be done for whatever reason. So technically the purchase shouldn't have gone through but google charges it no matter what since it does that as soon as we confirm a purchase.
I should mention that the API requires data from the Purchase object, which contains the purchaseToken and the subscriptionIdstrong text from the onPurchasesUpdated method.
My question is how are people overcoming this? As long asBillingClient.BillingResponseCode.OK is the response code the purchase goes through regardless of what the backend thinks. I don't believe I have control over the BillingResult.getResponseCode().

Should we unlock in-app item after purchase is success (onPurchasesUpdated), or after acknowledgement is sucess (onAcknowledgePurchaseResponse)?

Ever since in billing library 2 and 3, there is an extra acknowledgement step we need to perform, after purchasing is success.
When purchasing is success, the following call-back will be triggered.
public interface PurchasesUpdatedListener {
void onPurchasesUpdated(BillingResult billingResult, java.util.List<com.android.billingclient.api.Purchase> list);
}
When acknowledgement is success, the following call-back will be triggered.
public interface AcknowledgePurchaseResponseListener {
void onAcknowledgePurchaseResponse(BillingResult billingResult);
}
In success case, should we unlock in-app purchase item, when purchasing is success, or acknowledgement is sucess?
InApp Products cannot be bought after a purchase was made. We need to consume it after a
successful purchase, so that we can purchase again and it will become available for the
next time we make purchase of the same product that was bought before.
You must acknowledge all the purchases that are non consumable i.e subscriptions must be acknowledged. Here is a reference code or you
/**
* If you do not acknowledge a purchase, the Google Play Store will provide a refund to the
* users within a few days of the transaction. Therefore you have to implement
* [BillingClient.acknowledgePurchaseAsync] inside your app.
*
* #param purchase list of Purchase Details returned from the queries.
*/
private fun acknowledgeNonConsumablePurchasesAsync(purchase: Purchase) {
val acknowledgePurchaseRunnable = Runnable {
val params = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
myBillingClient.acknowledgePurchase(
params
) { billingResult: BillingResult ->
if (billingResult.responseCode == BillingResponseCode.OK) {
LogUtils.d(TAG, "onAcknowledgePurchaseResponse: " + billingResult.responseCode)
} else {
LogUtils.d(TAG, ("onAcknowledgePurchaseResponse: " + billingResult.debugMessage))
}
}
}
}
For Consumables this should do
/**
* Consumes InApp Product Purchase after successful purchase of InApp Product Purchase. InApp
* Products cannot be bought after a purchase was made. We need to consume it after a
* successful purchase, so that we can purchase again and it will become available for the
* next time we make purchase of the same product that was bought before.
*
* #param purchase the purchase result contains Purchase Details.
*/
val onConsumeListener =
ConsumeResponseListener { billingResult: BillingResult, purchaseToken: String ->
// If billing service was disconnected, we try to reconnect 1 time
// (feel free to introduce your retry policy here).
if (billingResult.responseCode == BillingResponseCode.OK) {
LogUtils.d(TAG, "onConsumeResponse, Purchase Token: $purchaseToken")
} else {
LogUtils.d(TAG, "onConsumeResponse: " + billingResult.debugMessage)
}
}
// Creating a runnable from the request to use it inside our connection retry policy below
val consumeRequest = Runnable {
// Consume the purchase async
val consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
myBillingClient!!.consumeAsync(consumeParams, onConsumeListener)
}
Please review the section "Verify purchases before granting entitlements".
https://developer.android.com/google/play/billing/security#verify
You should unlock content only after verifying via your backend server that the purchase was legitimate.
You implement onPurchasesUpdated, to get notifications for purchases updates initiated both within or outside your app.
If you don't acknowledge a purchase, the purchase will be automatically refunded. You implement onAcknowledgePurchaseResponse, to receive a notification that the acknowledgement of the purchase operation is complete.
But to know if it is a legitimate purchase, you must verify you the purchase is legitimate before granting entitlements.
A special case of sensitive data and logic that should be handled in
the backend is purchase verification. After a user has made a
purchase, you should do the following:
Send the corresponding purchaseToken to your backend. This means that you should maintain a record of all purchaseToken values for all
purchases.
Verify that the purchaseToken value for the current purchase does not match any previous purchaseToken values. purchaseToken is globally
unique, so you can safely use this value as a primary key in your
database.
Use the Purchases.products:get or Purchases.subscriptions:get endpoints in the Google Play Developer API to verify with Google that
the purchase is legitimate.
If the purchase is legitimate and has not been used in the past, you can then safely grant entitlement to the in-app item or subscription.
For subscriptions, when linkedPurchaseToken is set in Purchases.subscriptions:get, you should also remove the
linkedPurchaseToken from your database and revoke the entitlement that
is granted to the linkedPurchaseToken to ensure that multiple users
are not entitled for the same purchase.
hence you should unlock content only after all three are completed.
onPurchaseUpdated, for initially knowing that a purchase is complete.
onAcknowledgePurchaseResponse, so you know the acknowledgement is done, and the purchase will not be automatically refunded
you need to verify via your backend server that the purchase was legitimate.
When you have done all three, it is safe to unlock your purchase. If you do not do all three, there is a risk of unlocking content that for a purchase that has either been refunded or is illegitimate.

Android billing null purchases list

I'm trying to implement In-App purchases using 'com.android.billingclient:billing:2.0.3' library.
the app is released in the Internal test track, and have one managed product, and a single tester (another account of mine), I managed to launch a billing flow and buy the product, but I didn't acknowledge or consume the purchase, in spite of this the responseCode is now always ITEM_ALREADY_OWNED , I thought that if the purchase is not acknowledged within 5 minutes (for testers accounts) it will be refunded. why wasn't it ?
the second question is that I'm trying to consume the product so that i can continue the implementation using my only tester account, here's the onPurchasesUpdated function
override fun onPurchasesUpdated(
billingResult: BillingResult?, purchases: MutableList<Purchase>?) {
val resCode=billingResult!!.responseCode
println(resCode) // prints 7 i.e ITEM_ALREADY_OWNED
println(purchases!!.size) // nullPointerException
}
the purchases list is null, what am I doing wrong ?
please let me know if you want more details, thanks.
i had the same error(i'm using java, so code is a little different)
#Override
public void onPurchasesUpdated(BillingResult billingResult, #Nullable List<Purchase> list) {
in my case list was null, so what i did was just called queryPurchases() to load purchases again
List<Purchase> list2 = billingClient.queryPurchases(BillingClient.SkuType.INAPP).getPurchasesList();
creation of billingClient is in documentation

Can't re-buy in-app billing item on Google Play after it was refunded

I implemented in-app billing into my app and am now testing its handling of refunds.
I bought my app's managed in-app billing item with a test account and refunded it. My app got the refund broadcast as expected and it sees that the item was refunded when restoring transactions, so everything is good up to that point.
My problem is that I can't re-buy the item to test other scenarios.
When I try to purchase the item, the Google Play interface comes up and displays an error message saying "You already own this item." with 2 buttons "OK" and "Details".
If I press details, Google Play crashes and I return to my app.
Did anyone have a similar experience?
Is it forbidden for a user to purchase an in-app item if they previously had it refunded?
I was seeing the same issue. GP crash and everything.
In addition to waiting a few hours, you may want to open up 'Google Play' app info and clear cache and clear data. This solved it for me. It appears GP caches purchase information on the device and only checks Google's servers rarely, if ever, for refund information.
Update:
You may also want to kill the Google Play process since it appears to keep purchase info in memory too.
I asked Google about this issue and they told me that it's not possible to re-buy an in-app billing item on Google Play if it was previously refunded.
But when I tried to buy it again about 24 hours later, the purchase went through ...
So it looks like it's possible to re-buy, but only after some delay.
I know this is an old question, but i have been looking for an answer to this same question and eventually came to my own conclusion. Google doesn't spell it out, but I believe they want you to decide on your own logic as to how to handle cancelled and refunded purchases. Another point to keep in mind is that there there is essentially no difference between a consumable and non consumable managed product. All managed products are consumable.
For me, when a user cancels a purchase, or if I decide to give the user a refund, what I want to happen is that 1) the user receives their money back and 2) the user loses access to the paid feature and 3) the user has the option to purchase the feature again if they choose.
What I did was to check the purchaseState of the purchase on my back end server using the in-app billing API. If the returned purchaseState is a 1 (canceled) or 2 (refunded), I consume the purchase in my app. Google handles item 1, giving the user their money back. The logic in my app handles 2, locking access to the paid features. Consuming the purchase handles 3, giving the user the option to purchase the feature again.
The basic gist of it is, when a purchase is sent to my back end server for verification, I check the purchase state. If the purchase state is a 1 or a 2, I return an appropriate code to my app. When my app receives the code indicating the purchase is cancelled or refunded, my app consumes the purchase.
I use the PHP version of the API, so my simplified code to get the purchase state is :
$purchases = $service->purchases_products->get($packageName, $productId, $purchaseToken);
$purchaseState = $purchases->getPurchaseState();
if($purchaseState === 1){
$serverResponseCode = 3;
}
if($purchaseState === 2){
$serverResponseCode = 4;
}
...and then in my app, I check the server response codes.
if(serverResponseCode == 3 || serverResponseCode ==4 ){
lockFeatures();
ConsumeParams params = ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();
billingClient.consumeAsync(params, listener);
}
I hope this helps someone else looking for an answer to this problem.
In case somebody needs android and not kotlin code. All the explanation that smitty wrote:
When starting up the application , You have to check queryPurchases and look for the refunded items.
like that:
if (purchase.getPurchaseState() != Purchase.PurchaseState.UNSPECIFIED_STATE)
{
handleConsumablePurchasesAsync(purchasesList);
return false;
}
Than you CONSUME this item.
smitty1 is a Genius
private void handleConsumablePurchasesAsync(List<Purchase> consumables) {
Log.d(TAG, "handleConsumablePurchasesAsync");
for (Purchase purchase : consumables) {
ConsumeParams params = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.consumeAsync(params, (billingResult, purchaseToken) -> {
if (billingResult.getResponseCode() == OK) {
Log.d(TAG, "Consumed the old purchase that hasn't already been acknowledged");
} else {
Log.d(TAG, "Error consume the old purchase that hasn't already been acknowledged -> %s" + String.valueOf(billingResult.getResponseCode()));
}
});
}
}
I noticed that by checking the Remove Entitlements field on the refund page, you will be able to re-buy the product outright without waiting as suggested by the accepted answer.

Android billing - it says checkBillingSupported() is deprecated. What should I use instead?

Android documentation says that this method is deprecated, but I do not see what else I can use instead.
Basically, I am trying to do something like this:
if (mBillingService.requestPurchase(issueProductIdPsych, Consts.ITEM_TYPE_INAPP , null))
{
// Check what happened? Did the person finish the purchase? did they cance?
if(mBillingService.checkBillingSupported(Consts.ITEM_TYPE_INAPP))
{
}
//BillingHelper.requestPurchase(mContext, "android.test.purchased");
// android.test.purchased or android.test.canceled or android.test.refunded
}
What is the correct way to accomplish checking what the end of the purchase request was?
I have a buy button like this:
buy.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v)
{
But I am not really clear what needs to be done next.
Thanks!
Wish it was simpler to just replace the code with what you need, but apparently google describes an alternative option. You'd have to implement the MarketBillingService interface. So it's just a little tweak in design. Fortunately, they show you how to accomplish this.
http://developer.android.com/guide/google/play/billing/billing_integrate.html
Go down to the topic where it says "Creating a Local Service". The subcategories are:
Binding to the MarketBillingService
Sending billing requests to the MarketBillingService
Verifying that in-app billing is supported (CHECK_BILLING_SUPPPORTED)
Making a purchase request (REQUEST_PURCHASE)
To paraphrase what was written, I'll just describe it here:
In the category: "Verifying that in-app billing is supported (CHECK_BILLING_SUPPPORTED)"
They request that you use the sendBillingRequest(). This allows you to send five different types of billing requests:
CHECK_BILLING_SUPPORTED—verifies that the Google Play application supports in-app billing and the version of the In-app Billing API available.
REQUEST_PURCHASE—sends a purchase request for an in-app item.
GET_PURCHASE_INFORMATION—retrieves transaction information for a purchase or refund.
CONFIRM_NOTIFICATIONS—acknowledges that you received the transaction information for a purchase or refund.
RESTORE_TRANSACTIONS—retrieves a user's transaction history for managed purchases.
Says you have to create a bundle before you perform the request.
Here is an example of how to perform an acknowledgement:
Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
request.putStringArray(NOTIFY_IDS, mNotifyIds);
Bundle response = mService.sendBillingRequest(request);
Once you retrieve the response, check the contents within the Bundle, there will be three key items: RESPONSE_CODE, PURCHASE_INTENT, and REQUEST_ID. The RESPONSE_CODE key provides you with the status of the request and the REQUEST_ID key provides you with a unique request identifier for the request. The PURCHASE_INTENT key provides you with a PendingIntent, which you can use to launch the checkout UI.

Categories

Resources