Open OlexiyKravchuk opened 4 years ago
Hi!
I think this is more of a problem of the Google Play Billing library.
According to their docs, queryPurchases
returns all owned, non-consumed items.
And here they say that you need to wait for the purchase to complete if you don't want to check on an external backend server.
Note: Because consumption requests can occasionally fail, you must check your secure backend server to ensure that each purchase token hasn't been used so your app doesn’t grant entitlement multiple times for the same purchase. Alternatively, your app can wait until you receive a successful consumption response from Google Play before granting entitlement. If you choose to withhold purchases from the user until Google Play sends a successful consumption response, you must be very careful not to lose track of the purchase after the consumption request.
I think the easiest solution would be waiting for your purchase status to become PURCHASED
(= 1, https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState) before consumption.
Hello! Thanks for the answer. And at the same time the problem is a little different ... The fact is that even when the purchase has already been paid for, it is then confirmed and consumed. And then, if copies of the same product are sold, then the token will no longer indicate previous purchases, but only the last purchase of a copy of this product. And if the buyer decides to withdraw those funds that he has already paid for some of the previous transactions except for the last one, then there is no mechanism in the application to find out about such an action, and I worry that this is a back door for abuse and cheating. I would like to clarify that I do not have a server for storing data, but even if I did have one, it didn’t secure the system from such actions, because there is no way to link the cancellation of payment and the state of the balance in the player’s account.
@OlexiyKravchuk this is then a problem with the Google Play Billing API and not with this plugin, isn't it?
@timoschwarzer Yes I understand this. By the way. Here you say check status == 1 == PURCHASED but in this line #https://github.com/godotengine/godot-google-play-billing/blob/d5d5aaf4342b3afaec3f1b63d95622ff8624fdc2/godot-google-play-billing/src/main/java/org/godotengine/godot/plugin/googleplaybilling/GodotGooglePlayBilling.java#L196 it has a different meaning, or am I confusing something? However, I'm sure I'm confusing something because I'm already dizzy from switching all windows with JAVA code. and comparing all parameters ... This plugin needs very serious and detailed documentation, it is hellishly painful to work, relying on the source code of a language unknown to me, as if it were documentation.
And I found another problem, the Purchase class of the plugin is missing a few elements declared here ...
this is...
boolean equals (Object o) AccountIdentifiers getAccountIdentifiers () String getDeveloperPayload () String getOriginalJson () int hashCode () String toString ()
Maybe create a separate report for this?
Oh I found what the confusion is ... PurchaseState != Status
I tried to iterate over everything again today to organize my thoughts, it seems to me that it would be reasonable to rename the return value from "status" to ResponseCode as indicated in this document ...
but adjusted for the internal style of Godot, it will turn out - "response_code" to correspond to the original meaning of the agreements and avoid such confusion as happened to me.
No, what I wrote in the previous message is not correct! Because I rechecked the code for the hundredth time and next to "status", there is already a value for "response_code". O_o Again in this part of the code ...
Ie it is absolutely not clear where did the value of "status" come from? Why is there such a construction?
It might be easier to just specify "response_code" and "debug_message" like this ...
public Dictionary purchase(String sku) {
if (!skuDetailsCache.containsKey(sku)) {
Dictionary returnValue = new Dictionary();
returnValue.put("response_code", null);
returnValue.put("debug_message", "You must query the sku details and wait for the result before purchasing!");
return returnValue;
}
SkuDetails skuDetails = skuDetailsCache.get(sku);
BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
BillingResult result = billingClient.launchBillingFlow(getActivity(), purchaseParams);
Dictionary returnValue = new Dictionary();
returnValue.put("response_code", result.getResponseCode());
returnValue.put("debug_message", result.getDebugMessage());
return returnValue;
}
and that's enough?
public Dictionary queryPurchases(String type) {
Purchase.PurchasesResult result = billingClient.queryPurchases(type);
Dictionary returnValue = new Dictionary();
returnValue.put("response_code", result.getBillingResult().getResponseCode());
returnValue.put("debug_message", result.getBillingResult().getDebugMessage());
returnValue.put("purchases", GooglePlayBillingUtils.convertPurchaseListToDictionaryObjectArray(result.getPurchasesList()));
return returnValue;
}
I am quite a noob in JAVA so there may be errors in my optimization examples.
@OlexiyKravchuk status
is an item of the Error
enum (https://docs.godotengine.org/en/stable/classes/class_@globalscope.html)
If it's not difficult for you, could you fix it? I mean that this status there is clearly superfluous and even harmful, it simply duplicates the "response_code" but at the same time it cuts down the information content of debugging and complicating the code is just confusing. As a result, it is not possible to receive a purchase request in the _on_connected () function, I just get a message that the request failed and fake code 1, instead of "debug_message" explaining why, and a valid "response_code" to handle the exception.
By the way, there is a similar problem in the arguments passed to the onPurchasesUpdated (billingResult: BillingResult, purchases: List
It is not possible to handle exceptions in the plugin
You can handle exceptions, actually. If the response code is not OK
then the purchase_error
signal will be fired with the response code and the debug message. (AFAIK the debug message is always empty for succeeded purchases, so it is not relevant for purchases_updated
)
I mean that this status there is clearly superfluous and even harmful
No, for this simple reason: I wanted an error indicator for all methods that return a dictionary. There are cases where no response_code
exists, and I wanted that Godot users can just look at status
and see if it's an error or not. Also, this would break the API.
As a result, it is not possible to receive a purchase request in the _on_connected () function, I just get a message that the request failed and fake code 1, instead of "debug_message" explaining why, and a valid "response_code" to handle the exception.
You get those by subscribing to the connect_error
signal.
https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.voidedpurchases/list
@timoschwarzer hi timoschwarzer thanks for the effort publishing this amazing plug-in. Although I took a couple of weeks to sort everything out. and I had a similar problem like the one @OlexiyKravchuk had.
Within this plug-in, I haven't found a way to check if end-users refund their purchase or not. I run a couple of test purchase myself, and even when I refund the order, the purchase_status from queryPurchases() remains 1 for weeks (likely forever).
The record will remain in the purchase history until I use consumePurchase() to purchase the item again. then I found this article and trying to learn and implement it into your plug-in.
just started, and haven't got any luck yet, but hope this helps.
again, thanks for the plug-in, it's a very good start to learn.
If the user buys the same product (SKU) in the store 3 or more times, the number of coins after payment is immediately consumed and credited to the player's internal account. But after that, the player cancels the first two purchases. And I could not find a way to determine what happened and how after that it handles such a situation in the application. I read the official documentation of Godot, there was no such information, there is a demo example, but it also does not have such an example, I also looked at the documentation from Google, but the Plugin provides a significantly reduced API, and I do not know JAVA well enough to understand what it is and where it is transferred, I have spent almost a month to understand everything, and ran into this question. So I don't see a way to find out using the transaction token that the transaction is no longer valid? Please, any example with clarification, because the fields of returned tributes through signals are not documented anywhere.