ProcessReceipt issue

So I was making a ProcessReceipt function that allows buying devproducts for someone else, as well as keep the bought but unused devproducts in the mail, and I got everything working, I found no issues until release of the update.

After the release I got a bug report which led me to a conclusion that I left a little mistake that caused the function to not return any value if you leave while devproduct is executing. That person who reported the bug keeps getting the bought devproduct every time they join the game which is not that pleasant even for them. I fixed it as soon as I found the reason of it, but even on updated servers after returning PurchaseGranted, the issue with that exact person didn’t stop from happening.

I noted that the PurchaseId is always the same, so it couldn’t be more products or more failed, purchase, I confirmed that the function does return PurchaseGranted via prints. I have no idea what can be possibly done from my side to solve the issue before the refund.
Also, I couldn’t expect such behavior because during studio tests, which I made a lot, since I didn’t need to spend robux on it there, such behavior does not happen.

I am wondering if there is anything I can do about the issue sooner than they just get a refund. Or well, at least report the issue

1 Like

Can you include your ProcessReceipt function so we can verify you are returning the PurchaseGranted value correctly?

2 Likes

of course.

local SuccessfulPurchase = Enum.ProductPurchaseDecision.PurchaseGranted
local FailedPurchase = Enum.ProductPurchaseDecision.NotProcessedYet

local function ProcessReceipt(receiptInfo)
	local Buyer = Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not Buyer then
		GiftTo[Buyer] = nil
		return FailedPurchase
	end
	local Good = module[receiptInfo.ProductId]
	if not Good then
		return FailedPurchase
	end
	local Receiver = GiftTo[Buyer] or Buyer
	GiftTo[Buyer] = nil
	if not Receiver.Parent then
		return FailedPurchase
	end
	local Given
	local succ, result = pcall(function()
		if Receiver == Buyer then
			local con
			local Thread = coroutine.running()
			con = Players.PlayerRemoving:Connect(function(plr)
				if plr == Receiver then
					con:Disconnect()
					Given = true
					-- I send the product to user's mail
					local succ, err = coroutine.resume(Thread, SuccessfulPurchase)
					if not succ then
						warn(err)
					end
				end
			end)
			local s, Success = pcall(function()
				-- calling method here which returns true if everything went as intended
			end)
			con:Disconnect()
			if (not s or not Success) and not Given then
				-- if there was an error and the player hasn't received it from the .PlayerRemoving event, give the product to player through mail
			end
			return SuccessfulPurchase
		else
			-- if gifting then send the product to the player
			return SuccessfulPurchase
		end
	end)
	print(succ, result, Buyer) -- does confirm that the pcall ran successfully and the result is also a success
	if succ and result then -- thus, this is true
		return result -- which is equal to SuccessfulPurchase
	end
	return FailedPurchase -- and it does not reach here
end

Also wanna mention that we tried to solve the issue by accepting the product (thus, not touching the PlayerRemoving part), but it didn’t help.

Alright, new details, the issue still isn’t fixed. I will edit the system to work differently. Even tho it returns SuccessfulPurchase, it probably just doesn’t work when the player is leaving, and using it after coming back also does not solve the problem, as it appears again every time I join a new server. And yes, it does return PurchaseGranted in all situations

Three questions:

  • What & when does the GiftTo table get populated with and how is it meant to be used?
  • Do you know know if the player experiencing the issue purchased the product for himself or for another player?
  • How does the mail system work? I’m not sure if the code for it is removed here or if I’m overlooking something

Don’t worry about these, I know that they bought for themselves and I just got bugged myself so I edited the code and found the solution of the issue. Mail system works perfectly and now so does everything.

I can’t tell exactly what caused the issue, but it seems like ProcessReceipt function doesn’t really like yielding. I created a separate thread for the script execution/sending mail to the player and the issue faded away after the next rejoin. The prints were all the same. I think it should be documented somewhere because I never found anything about it not working well with yields, I also know that roblox suggests using DataStores which do yield, which means it is supposed to work fine with yields. But as experience shows, it doesn’t.

Hope this post will bring a solution to those who face the same problem, and I hope Roblox staff also notice this.

1 Like

To be clear, yielding is actually a very important functionality within ProcessReceipt to ensure the data is saved before you return PurchaseGranted (DataStore calls are asynchronous, so you have to yield to save their data while handling the receipt). The issue would not be yielding within a ProcessReceipt function, this is supported behavior.

The part I got confused at in your code is when you call coroutine.resume, because you never yield the thread before calling resume, which would probably result in trying to resume a dead thread. This would throw an error, but it would only kill the response to the PlayerRemoving event. Perhaps the yielding happens in the mail system though, that is why I asked if there is missing code here. What are you trying to accomplish by resuming the thread?

(I know you think you’ve come to a solution already, but it would be nice to figure out why it’s not working to be sure you’ve fixed it)

1 Like

the line with calling method is yielding for certain products, so yeah, I needed to resume it in case player leaves cuz infinite yield. I know that it is supposed to be saved, that’s where mail comes in play, it saves in the player’s data so there is no need to worry about using more datastores. The mail is not yielding and reacts fast. Resuming coroutine causes an error in the method call but it makes it continue and thus reach the finish where it prints that Purchase is granted

1 Like