Yes i had the exact problem where the callback for the relevant state/phase was being triggered anywhere between 1-4 times. Initially my workaround was to use a timer of a few millis and just block the actions by using a boolean for the duration of that timer but then i noticed that this was only happening for me in live builds.
Perhaps it’s a problem that was temporarily fixed and then returned in one of the later commits of this plugin so I’ll check again tonight and see if i can reproduce it.
yeah its very strange. i just tried testing with using
[“plugin.google.iap.billing.v2”] =
{
publisherId = “com.solar2d”,
version = “v10”,
},
as @colinmorgan suggested. also tried v9
but still the same problem with multiple purchase states.
Ok heres some info after banging my head left right and center.
i found some apps are working correctly and some are not. And i found the difference is when i have
store = require( “plugin.google.iap.billing.v2” )
and initialize it in
main.lua
and then call store.purchase from a scene. It is working as intended. Although a major issue as described in the last paragraph of this.
The issue i was having with the apps that had the multiple purchase states. I had the following in the scene lua file (lets say example shop.lua)
local store = require( “plugin.google.iap.billing.v2” )
and initialize it in the shop.lua file.
What seems to be happening, every time it opens that scene, it is initialising the store over and already initialised store. which should not be the case, as store is set as local store. have never encountered this problem before. It seems each time you initialize the store even if its local it is being stored in memory so each time it is initialized its another instance of it which is active.
Now i have another totally WIERD issue. So if the store is initialised in main.lua. every time i close the app, make sure it is swiped closed and then reopen the app, it starts from the splash screen, it seems to be reinitialising the store even though in the background the previous store seems to be still functioning??? so every time i open the app fresh from the splash screen, for example if i had opened the app twice, the purchase state runs twice. if i close the app and start it fresh again, the purchase state will run 3 times. it seems the transactionCallback is getting stuck in memory and being duplicated everytime it is initialised even if the app is opened from the splash screen hence the multiple purchase.
I’m going to cache transaction numbers as well from the section of code that provides the purchased award (i.e. Lives and Gold Coins).
Quick question - I’ve been thinking some time to ignore purchases where the Google Play receipt does not include “GPA”. Have you found some purchases without GPA that are legit? For example, a typical receipt looks like {“orderId”:“GPA.3343-7306-9404-38030”… by requiring GPA, I’m thinking to potentially eliminate purchases that don’t occur through Google Play, presumably hacked orders. What has been your experience with this, and do you think my idea to require the GPA is a good one?
@anon63346430 although it’s not required to update before then… Any new updates you put live from 1st November DOES need to use the new billing library as the original post explains
Is anyone else getting the multiple purchase states on their builds? or is it just me? i have tried high and low to find the problem but cant seem to find a fix. im wondering if there is something im doing or the plugin itself?
I allow players to make fake purchases (I get hundreds per day) but I then ban them from all internet features - this way no other player will see them.
I used to block players but got hundreds of 1 star reviews - I know right?
So now I let them cheat but block them from other players and remove the from my KPIs so I worry not about them.
Which issue in particular? (There have been a lot brought up in this thread…) I’m running the latest version of the plugin with no issues. +/- 1,000 transactions in the last 30 days, nothing out of the ordinary.
Im still getting the multiple purchase states. its very evident especially on consumables.
Although i would like to say it is not happening every time!
The way i seem to be able to catch it is, im adding a alert on transaction.state == “purchased” where productIdentifier is whats being purchased. EG 1000stars
When i go into the shop scene, and purchase, it shows the alert once. and 1000 stars are credited. (ALL good, working as expected)
When i exit the shop scene back to the menu scene, then open the shop scene again, purchase the 1000 stars again, I get two or three alerts, and 3000 stars are added. hence it seems to be looping through the same transaction.
For the time being i have added @anon63346430 solution above but caching transaction receipts so it checks if that purchase is already given, and if it is, dont give again. although this is a workaround to what is a problem as it loops through the same transaction.
local function transactionCallback( event )
if ( event.name == "init" ) then
if not ( event.transaction.isError ) then
if PriceLoaded == nil then
store.loadProducts( listOfProducts, productCallback )
end
else -- Unsuccessful initialization; output error details
print( event.transaction.errorType )
print( event.transaction.errorString )
end
-- Store transaction event
elseif ( event.name == "storeTransaction" ) then
local transaction = event.transaction
if transaction.state == "purchased" then
if transaction.productIdentifier == "carstars1000" then
GiveStars(1000)
local alert = native.showAlert("1000 stars","1000 stars added", { "OK" }, onAlertCompleteIAP )
timer.performWithDelay(1000,ConsumeIt)
end
elseif transaction.state == "restored" then
-- You'll never reach this transaction state on Android.
elseif transaction.state == "refunded" then
-- Android-only; user refunded their purchase
--local productId = transaction.productIdentifier
-- Restrict/remove content associated with above productId now
elseif transaction.state == "cancelled" then
-- Transaction was cancelled; tell you app to react accordingly here
elseif transaction.state == "failed" then
-- Transaction failed; tell you app to react accordingly here
end
if (transaction.isError) then
local errorType=tostring(transaction.errorType)
--errorType==6, errorString="Server error, please try again."
--errorType==7, errorString="Unable to buy item (response: 7: Item Already Owned)"
if errorType=="7" then
ConsumeIt()
end
end
-- The following must be called after transaction is complete.
-- If your In-app product needs to download, do not call the following
-- function until AFTER the download is complete:
store.finishTransaction( transaction )
end
end
I noticed you are not checking for "consumed". Potentially your consumeIt() may try and consume before the transaction has finished processing. I keep trying to consume until it actually gets consumed. You also are not handling deferred purchases. (I’ve stripped my code down to keep it simple)
--process transaction
if transaction.state == "purchased" then
if _isGoogle then
--Google purchases must be consumed
if timerID then timer.cancel(timerID) end
timerID = timer.performWithDelay( 5000, function()
store.consumePurchase( transaction.productIdentifier )
end, -1 )
end
--award player now
if isOrderNumberUnique(transaction.identifier) then
iap:doCredit(transaction.productIdentifier)
if callback then callback("yes"); callback = nil end
else
if callback then callback("no"); callback = nil end
end
elseif transaction.state == "consumed" then
--this is Google only
if timerID then timer.cancel(timerID) end
elseif transaction.state == "failed" then
if callback then callback("failed"); callback = nil end
elseif transaction.state == "pending" then
if callback then callback("pending"); callback = nil end
--store this for later
if string.find(iap.pendingPurchases or "", transaction.identifier or "", 1, true) == nil then
print("IAP: Storing "..transaction.identifier.." for later")
iap.pendingPurchases = iap.pendingPurchases..(transaction.identifier or "")..","
end
elseif transaction.state == "cancelled" then
if callback then callback("no"); callback = nil end
end
store.finishTransaction( transaction )