A new vulnerability that may affect your game is if you use PromptProductPurchaseFinished, exploiters are now able to fake / spoof buy a dev product with SignalPromptProductPurchase where you can go call to the server that you have either failed or bought a product, this also affects PromptGamepassPurchaseFinished (make it recheck gamepass ownership when called instead of rewarding) and PromptPurchaseFinished, which may be difficult to fix, depending on your game, as you can call this inside a prompt, this may often affect ugc games.
Example of SignalPromptProductPurchase being used (this is one of my friends not me)
PromptProductFinished vulnerabilities can be fixed by using ProcessReceipt My example for handling UGC games that allows you to buy Free UGCs for robux with a dev product is:
local DEV_PRODUCT_ID =tonumber(game:GetService("ReplicatedStorage"):WaitForChild("DEVPRODUCTID").Value)
local productIdUGC = game:GetService("ReplicatedStorage"):WaitForChild("UGCID").Value
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local bought = {}
local function processReceipt(receiptInfo)
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
print(player.Name .. " - dev product id", receiptInfo.ProductId)
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
if receiptInfo.ProductId == DEV_PRODUCT_ID then
bought[receiptInfo.PlayerId] = DEV_PRODUCT_ID
return Enum.ProductPurchaseDecision.PurchaseGranted
end
return Enum.ProductPurchaseDecision.NotProcessedYet
end
MarketplaceService.ProcessReceipt = processReceipt
local success, result = pcall(function()
MarketplaceService.ProcessReceipt = processReceipt
end)
if not success then
warn("processreceipt:", result)
end
MarketplaceService.PromptProductPurchaseFinished:Connect(function(player, productId, wasPurchased)
if tonumber(productId) == DEV_PRODUCT_ID and wasPurchased == true and bought[player] == DEV_PRODUCT_ID then
pcall(function() MarketplaceService:PromptPurchase(game.Players:GetPlayerByUserId(player), productIdUGC)
end)
bought[player] = nil
end
end)
You can download a copy of my game here where I use processreceipt with PromptProductPurchaseFinished. Dev Product Game Testing - Roblox
Here is my version for my game Be Silent For UGC where I use ProcessReceipt to check multiple dev products at once.
local MarketplaceService = game:GetService("MarketplaceService")
game.ReplicatedStorage.PromptProductPurchase.OnServerEvent:Connect(function(player, productId)
MarketplaceService:PromptProductPurchase(player, productId)
end)
local item_id = game.ReplicatedStorage:WaitForChild("ITEM_ID", 5).Value
local devproducts = {
[1788956201] = "Mute All",
[1788956710] = "Instant UGC",
[1788956860] = "Instant Win",
[1788957045] = "Prompt All",
[1788956431] = "Reset Servers"
}
local Players = game:GetService("Players")
local bought = {}
local function processReceipt(receiptInfo)
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
print(player.Name .. " - dev product id", receiptInfo.ProductId)
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
if devproducts[receiptInfo.ProductId] then
bought[receiptInfo.PlayerId] = receiptInfo.ProductId
return Enum.ProductPurchaseDecision.PurchaseGranted
end
return Enum.ProductPurchaseDecision.NotProcessedYet
end
MarketplaceService.ProcessReceipt = processReceipt
local success, result = pcall(function()
MarketplaceService.ProcessReceipt = processReceipt
end)
if not success then
warn("processreceipt:", result)
end
MarketplaceService.PromptProductPurchaseFinished:Connect(function(player, productId, wasPurchased)
productId = tonumber(productId)
if wasPurchased and devproducts[productId] and player ~= 116781531 and bought[player] == productId then
bought[player] = nil
if productId == 1788956201 then
local muteValue = game.ReplicatedStorage:WaitForChild("Mute", 5)
muteValue.Value = muteValue.Value + 60
elseif productId == 1788956710 then
MarketplaceService:PromptPurchase(game.Players:GetPlayerByUserId(player), item_id)
elseif productId == 1788956860 then
for i = 1, 3 do
game.ReplicatedStorage:WaitForChild("Time", 5).Value = 1
end
print("Win")
elseif productId == 1788957045 then
for _, v in ipairs(game.Players:GetPlayers()) do
MarketplaceService:PromptPurchase(v, item_id)
end
print("Prompt All")
elseif productId == 1788956431 then
game:GetService("MessagingService"):PublishAsync("ResetProduct", "Reset " .. tostring(game.Players:GetPlayerByUserId(player).Name))
print("Reset")
end
end
end)
If your game does not prompt anything after a PromptProductPurchase then you do not need the script above, just replace it with ProcessReceipt as PromptProductPurchaseFinished is deprecated and the purchase goes through after the prompt is gone instead of instantly processing.
Roblox does know about this vulnerability but I do not know what they’re doing with it.
Note that you cannot fire these in any normal means, as it is used for corescripts, as you see in Roblox-Client-Tracker. or in the Code Snippet.
if requestType == RequestType.Product then
local playerId = (Players.LocalPlayer :: Player).UserId
MarketplaceService:SignalPromptProductPurchaseFinished(playerId, id, didPurchase)
elseif requestType == RequestType.GamePass then
MarketplaceService:SignalPromptGamePassPurchaseFinished(Players.LocalPlayer, id, didPurchase)
elseif requestType == RequestType.Bundle then
MarketplaceService:SignalPromptBundlePurchaseFinished(Players.LocalPlayer, id, didPurchase)
elseif requestType == RequestType.Asset then
MarketplaceService:SignalPromptPurchaseFinished(Players.LocalPlayer, id, didPurchase)
local assetTypeId = state.productInfo.assetTypeId
if didPurchase and assetTypeId then
-- AssetTypeId returned by the platform endpoint might not exist in the AssetType Enum
pcall(function() MarketplaceService:SignalAssetTypePurchased(Players.LocalPlayer, assetTypeId) end)
end
elseif requestType == RequestType.Premium then
MarketplaceService:SignalPromptPremiumPurchaseFinished(didPurchase or purchaseError == PurchaseError.AlreadyPremium)
elseif requestType == RequestType.Subscription then
MarketplaceService:SignalPromptSubscriptionPurchaseFinished(id, didPurchase or purchaseError == PurchaseError.AlreadySubscribed)
end
All these below are vulnerabilities, if you use the function minus the Signal, please be careful!
- SignalAssetTypePurchased
- SignalClientPurchaseSuccess
- SignalMockPurchasePremium
- SignalPromptBundlePurchaseFinished
- SignalPromptGamePassPurchaseFinished
- SignalPromptPremiumPurchaseFinished
- SignalPromptProductPurchaseFinished
- SignalPromptPurchaseFinished
- SignalPromptSubscriptionPurchaseFinished
- SignalPromptSubscriptionCancellationFinished
A video of the vulnerability (Game)
p.s roblox pls make a working processreceipt for gamepasses and regular asset purchases