Money not being refunded 3 days after a failed ProcessReceipt

So in my ProcessReceipt function I make 3 DataStore calls for renting private servers, however, the middle one reached a point where the Key was full and so would fail every time when trying to add another entry to it. This caused all of these purchases to NOT return “PurchaseGranted”, and so I expected the money would be returned in 3 days as it is written on the wiki.

However, I recently had a client make a purchase for a year long private server (7.5k R$), and like mentioned above, it failed to complete the datastore calls and so never returned “PurchaseGranted”. However, this client was never refunded his money, and I know this since I had around 12k in pending sales and just yesterday it dropped to like 5k, and my Sale of Goods went up by 7.5k. Also the client said he never received his 7.5k back.

So all this time I have been telling people if they received errors during purchases they would be refunded, however, this is not the case.

Format bug stuff below:

  • Pretty sure it happens every time, as some people who rented 1 Day private servers had also complained, and I assume they were not refunded.

  • Happens on Live Production

  • The purchase that was not refunded

  • When did the bug start happening? I think its been this way for a long time, I’ve had this problem before, but with smaller amounts, and the clients never got back to me on whether they were refunded or not.

  • I have already refunded the client so no need to refund him now.

I have had a few people complain about not getting items nor refunds, as two DataStore requests are done. I have yet to validate the purchase registered but I am thinking this bug is relevant.

Can you provide a link to the game where this bug occured? If possible it would also be good to include the code you use for processing receipts.

Place: https://www.roblox.com/games/527513446/Ultimate-Boxing

As much as my code is in functions elsewhere, the function names hopefully should be clear.

[code]
local NotProcessedYet = Enum.ProductPurchaseDecision.NotProcessedYet
local PurchaseGranted = Enum.ProductPurchaseDecision.PurchaseGranted
function MarketplaceService.ProcessReceipt(Reciept)
local PlayerId,PurchaseId,ProductId,CurrencySpent = Reciept.PlayerId,Reciept.PurchaseId,Reciept.ProductId,Reciept.CurrencySpent
if not PlayerId or not PurchaseId or not ProductId or not CurrencySpent then
warn(“PURCHASE FAILED - MISSING INFO”)
return NotProcessedYet
end

local Player = game.Players:GetPlayerByUserId(PlayerId)
if not Player then
	warn("PURCHASE OF "..tostring(ProductId).." FAILED - PLAYER NOT IN SERVER")
	return NotProcessedYet
end

local function AlertPlayerOfCompletion(Message)
	PurchaseCompleted:FireClient(Player,Message)
end

local function AnimateCoins(Amount)
	AnimateCoinsEvent:FireClient(Player,Amount)
end

if FastFlagService:GetFastFlag("ProcessProductPurchases") ~= true then
	warn("PURCHASE FAILED - PROCESSING DISABLED")
	AlertPlayerOfCompletion("Your purchase failed because purchase processing is disabled. Your Robux will be refunded in 3 days if it isn't processed.")
	return NotProcessedYet
end

PlayerDataService:AddProductPurchase(Player,ProductId,CurrencySpent)

local ProductName = IdToRobuxProduct(ProductId)
AddProductToTrackingByName(ProductName)
if ProductName == "DiamondBox" then
	if InventoryManagerService:AwardItem(Player,1,61) then
		AlertPlayerOfCompletion("Your purchase of a Diamond Box was successful! You now equip it in the inventory.")
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of a Diamond Box failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
elseif ProductName == "NeonBox" then
	if InventoryManagerService:AwardItem(Player,1,62) then
		AlertPlayerOfCompletion("Your purchase of a Neon Box was successful! You now equip it in the inventory.")
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of a Neon Box failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
elseif ProductName == "Coins50" then
	if ShopManagerService:AwardCoins(Player,50) then
		AlertPlayerOfCompletion("Your purchase of 50 coins was successful!")
		PlayerDataService:SavePlayer(Player)
		AnimateCoins(5)
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of 50 coins failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
elseif ProductName == "Coins250" then
	if ShopManagerService:AwardCoins(Player,250) then
		AlertPlayerOfCompletion("Your purchase of 250 coins was successful!")
		PlayerDataService:SavePlayer(Player)
		AnimateCoins(10)
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of 250 coins failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
elseif ProductName == "Coins1000" or ProductName == "Coins1000Discount" then
	if ShopManagerService:AwardCoins(Player,1000) then
		AlertPlayerOfCompletion("Your purchase of 1,000 coins was successful!")
		PlayerDataService:SavePlayer(Player)
		AnimateCoins(15)
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of 1,000 coins failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
elseif ProductName == "Coins2500" then
	if ShopManagerService:AwardCoins(Player,2500) then
		AlertPlayerOfCompletion("Your purchase of 2,500 coins was successful!")
		PlayerDataService:SavePlayer(Player)
		AnimateCoins(20)
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of 2,500 coins failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
elseif ProductName == "Coins5000" then
	if ShopManagerService:AwardCoins(Player,5000) then
		AlertPlayerOfCompletion("Your purchase of 5,000 coins was successful!")
		PlayerDataService:SavePlayer(Player)
		AnimateCoins(25)
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of 5,000 coins failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
elseif ProductName == "WoodCrate" then
	if InventoryManagerService:AwardItem(Player,3,1) then
		AlertPlayerOfCompletion("Your purchase of a Wood Crate was successful! You now open it in the inventory.")
		PlayerDataService:SavePlayer(Player)
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of a Wood Crate failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
elseif ProductName == "MetalCrate" then
	if InventoryManagerService:AwardItem(Player,3,2) then
		AlertPlayerOfCompletion("Your purchase of a Metal Crate was successful! You now open it in the inventory.")
		PlayerDataService:SavePlayer(Player)
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of a Metal Crate failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
elseif ProductName == "GoldCrate" then
	if InventoryManagerService:AwardItem(Player,3,3) then
		AlertPlayerOfCompletion("Your purchase of a Gold Crate was successful! You now open it in the inventory.")
		PlayerDataService:SavePlayer(Player)
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of a Gold Crate failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
elseif ProductName == "NeonCrate" then
	if InventoryManagerService:AwardItem(Player,3,4) then
		AlertPlayerOfCompletion("Your purchase of a Neon Crate was successful! You now open it in the inventory.")
		PlayerDataService:SavePlayer(Player)
		return PurchaseGranted
	else
		AlertPlayerOfCompletion("Your purchase of a Neon Crate failed. Your Robux will be refunded in 3 days if it isn't processed.")
	end
end
AlertPlayerOfCompletion("Your purchase failed because purchase of an internal server error. Your Robux will be refunded in 3 days if it isn't processed.")
return NotProcessedYet

end[/code]
The person mentioned never getting a response, so I am thinking it failed early or never started.

Game: https://www.roblox.com/games/146204374/HHCL-Lobby#
Its a universe, but the private servers are purchased from the Starting place (the lobby)

Code:

	
local PS = require(game.ServerScriptService.PRV) -- empty Mod script for sharing purchasing detail with this script
-- other varibles
--
--
MarketplaceService.ProcessReceipt = function(receiptInfo)
	print("Checking a Purchased")	
	--
	if receiptInfo.ProductId == PRV365 then
		local PRVSev = PS[tostring(receiptInfo.PlayerId).."_B"] -- fetch purchasing info
		if PRVSev and PRVSev[4] then -- this is renewing an already bought server (1 Data store request)
			PSD:UpdateAsync(tostring(PRVSev[4]),function(old)
				if old then
					--
					-- Data Stuff
					--
					PS[tostring(old.RCode or old.PlaceId)] = old -- pass data back to other script that called for purchase
				end
				return old
			end) -- if this datastore call fails, it can't return PurchaseGranted
			PS[tostring(receiptInfo.PlayerId).."_R"] = "Server Renewed Successfully"
			return Enum.ProductPurchaseDecision.PurchaseGranted		
		end
		if PRVSev then -- this is purchasing details (should always exist if purchase was made on this server)
			local TT = PRVSev[1]
			TT.Type = "Year"
			-- . . .
			-- other stuff
			-- . . .
			local PrivateId = 196235086
			local PrivCode = TS:ReserveServer(PrivateId) -- create server
			TT.PlaceId = PrivateId
			TT.PrivCode = PrivCode
			TT.UpdatedId = PrivateId
			--
			-- First Datastore call (Never fails, unless datastores are down)
			PSD:SetAsync(tostring(PrivCode),TT)
			--
			PS[tostring(PrivCode)] = TT -- caches it
			--
			-- Second Datastore call (this is the one that got full and so would always fail)
			PSD:UpdateAsync("PRV_List",function(old)
				if not old then
					local old2 = {}
					old2[PRVSev[2]] = {PRVSev[2],PrivateId,TT.Owner,PrivCode}
					if PS["PRV_List"] then
						PS["PRV_List"][PRVSev[2]] = {PRVSev[2],PrivateId,TT.Owner,PrivCode}
					end	
					--PS["PRV_List"] = old2
					return old2
				else
					old[PRVSev[2]] = {PRVSev[2],PrivateId,TT.Owner,PrivCode}
					if PS["PRV_List"] then
						PS["PRV_List"][PRVSev[2]] = {PRVSev[2],PrivateId,TT.Owner,PrivCode}
					end	
					--PS["PRV_List"] = old
					return old
				end	
			end)
			--
			-- Third Datastore call (has never gotten big enough to become full, and so always works)
			PSD:UpdateAsync(tostring(receiptInfo.PlayerId).."_QF",function(old)
				if not old then
					local old2 = {}
					table.insert(old2,{PRVSev[2],PrivateId,"Owner",PrivCode})
					PS[tostring(receiptInfo.PlayerId).."_QF"] = old2
					return old2
				else
					table.insert(old,{PRVSev[2],PrivateId,"Owner",PrivCode})
					PS[tostring(receiptInfo.PlayerId).."_QF"] = old
					return old
				end	
			end)
			print("Purchase of a Year Private Server Completed")
			PS[tostring(receiptInfo.PlayerId).."_R"] = "Server Bought Successfully"
			return Enum.ProductPurchaseDecision.PurchaseGranted
			
		else -- missing purchase details, so return purchase not granted
			print("error: Data Did Not Make it to Handle")
			PS[tostring(receiptInfo.PlayerId).."_R"] = "Failed to Buy Private Server. Data Was Lost"
			return Enum.ProductPurchaseDecision.NotProcessedYet -- failed to find a DataStore, try again
		end	
	end	
	return Enum.ProductPurchaseDecision.NotProcessedYet -- unknown DevProduct Purchased
--
-- . . . 
--
end

I just finished the product history viewer for Ultimate Boxing. There is no records of the logs, which means it is failing the DataStore requests.