Rowblox-V1/asset/backup/12

1296 lines
45 KiB
Plaintext

%12%
-- this script creates the gui and sends the web requests for in game purchase prompts
-- wait for important items to appear
while not Game do
wait(0.1)
end
while not game:GetService("MarketplaceService") do
wait(0.1)
end
while not game:FindFirstChild("CoreGui") do
wait(0.1)
end
while not game.CoreGui:FindFirstChild("RobloxGui") do
wait(0.1)
end
-------------------------------- Global Variables ----------------------------------------
-- utility variables
local RbxUtility = nil
local baseUrl = game:GetService("ContentProvider").BaseUrl:lower()
baseUrl = string.gsub(baseUrl,"/m.","/www.") --mobile site does not work for this stuff!
local doNativePurchasing = true
-- data variables
local currentProductInfo, currentAssetId, currentCurrencyType, currentCurrencyAmount, currentEquipOnPurchase, currentProductId, currentServerResponseTable, thirdPartyProductName
local checkingPlayerFunds = false
local purchasingConsumable = false
-- gui variables
local currentlyPrompting = false
local currentlyPurchasing = false
local purchaseDialog, errorDialog = nil
local tweenTime = 0.3
local showPosition = UDim2.new(0.5,-217,0.5,-146)
local hidePosition = UDim2.new(0.5,-217,1,25)
local isSmallScreen = nil
local spinning = false
local spinnerIcons = nil
local smallScreenThreshold = 450
local renderSteppedConnection = nil
-- user facing images
local assetUrls = {}
local assetUrl = "http://www.roblox.com/Asset/?id="
local errorImageUrl = assetUrl .. "42557901" table.insert(assetUrls, errorImageUrl)
local buyImageUrl = assetUrl .. "142494143" table.insert(assetUrls,buyImageUrl)
local cancelButtonImageUrl = assetUrl .. "142494219" table.insert(assetUrls, cancelButtonImageUrl)
local freeButtonImageDownUrl = assetUrl .. "104651761" table.insert(assetUrls, freeButtonImageDownUrl)
local loadingImage = assetUrl .. "143116791" table.insert(assetUrls,loadingImage)
-- user facing string
local buyHeaderText = "Buy Item"
local takeHeaderText = "Take Item"
local buyFailedHeaderText = "An Error Occurred"
local errorPurchasesDisabledText = "In-game purchases are disabled"
local errorPurchasesBuyRobuxText = "your account does not have enough Robux"
local errorPurchasesUnknownText = "Roblox is performing maintenance"
local purchaseSucceededText = "Your purchase of itemName succeeded!"
local purchaseFailedText = "Your purchase of itemName failed because errorReason. Your account has not been charged. Please try again soon."
local productPurchaseText = "Would you like to buy the itemName assetType for currencyTypecurrencyAmount?"
local productPurchaseTixOnlyText = "Would you like to buy the itemName assetType for currencyAmount currencyType?"
local freeItemPurchaseText = "Would you like to take the assetType itemName for FREE?"
local freeItemBalanceText = "Your balance of Robux or Tix will not be affected by this transaction."
local upgradeBCText = "You require an upgrade to your Builders Club membership to purchase this item. Click 'Buy Builders Club' to upgrade."
local productPurchaseWithMoreRobuxText = "Buy robuxToBuyAmount$R to get itemName assetType."
local productPurchaseWithMoreRobuxRemainderText = "The remaining purchaseRemainder$R will be credited to your ROBUX balance."
local balanceFutureTenseText = "Your balance after this transaction will be "
local balanceCurrentTenseText = "Your balance is now "
-- robux product arrays
local bcRobuxProducts = {90, 180, 270, 360, 450, 1000, 2750}
local nonBcRobuxProducts = {80, 160, 240, 320, 400, 800, 2000}
-------------------------------- End Global Variables ----------------------------------------
----------------------------- Util Functions ---------------------------------------------
function getSecureApiBaseUrl()
local secureApiUrl = baseUrl
secureApiUrl = string.gsub(secureApiUrl,"http","https")
secureApiUrl = string.gsub(secureApiUrl,"www","api")
return secureApiUrl
end
function getRbxUtility()
if not RbxUtility then
RbxUtility = LoadLibrary("RbxUtility")
end
return RbxUtility
end
function preloadAssets()
for i = 1, #assetUrls do
game:GetService("ContentProvider"):Preload(assetUrls[i])
end
end
----------------------------- End Util Functions ---------------------------------------------
-------------------------------- Accept/Decline Functions --------------------------------------
function removeCurrentPurchaseInfo()
currentAssetId = nil
currentCurrencyType = nil
currentCurrencyAmount = nil
currentEquipOnPurchase = nil
currentProductId = nil
currentProductInfo = nil
currentServerResponseTable = nil
checkingPlayerFunds = false
end
function userPurchaseActionsEnded(isSuccess)
checkingPlayerFunds = false
purchaseDialog.BodyFrame.AfterBalanceText.Visible = false
if isSuccess then -- show the user we bought the item successfully, when they close this dialog we will call signalPromptEnded
local newPurchasedSucceededText = string.gsub( purchaseSucceededText,"itemName", tostring(currentProductInfo["Name"]))
purchaseDialog.BodyFrame.ItemDescription.Text = newPurchasedSucceededText
local playerBalance = getPlayerBalance()
local keyWord = "robux"
if currentCurrencyType == Enum.CurrencyType.Tix then
keyWord = "tickets"
end
local afterBalanceNumber = playerBalance[keyWord]
purchaseDialog.BodyFrame.AfterBalanceText.Text = tostring(balanceCurrentTenseText) .. currencyTypeToString(currentCurrencyType) .. tostring(afterBalanceNumber) .. "."
purchaseDialog.BodyFrame.AfterBalanceText.Visible = true
setButtonsVisible(purchaseDialog.BodyFrame.OkPurchasedButton)
hidePurchasing()
else -- otherwise we didn't purchase, no need to show anything, just signal and close dialog
signalPromptEnded(isSuccess)
end
end
function signalPromptEnded(isSuccess)
closePurchasePrompt()
if purchasingConsumable then
game:GetService("MarketplaceService"):SignalPromptProductPurchaseFinished(game.Players.LocalPlayer.userId, currentProductId, isSuccess)
else
game:GetService("MarketplaceService"):SignalPromptPurchaseFinished(game.Players.LocalPlayer, currentAssetId, isSuccess)
end
removeCurrentPurchaseInfo()
end
function getClosestRobuxProduct(amountNeededToBuy, robuxProductArray)
local closest = robuxProductArray[1];
local closestIndex = 1
for i = 1, #robuxProductArray do
if ( math.abs(robuxProductArray[i] - amountNeededToBuy) < math.abs(closest - amountNeededToBuy) ) then
closest = robuxProductArray[i]
closestIndex = i
end
end
if closest < amountNeededToBuy then
closest = robuxProductArray[1 + closestIndex]
end
return closest
end
--todo: get productIds from server instead of embedding values
function getMinimumProductNeededForPurchase(amountNeededToBuy)
local isBcMember = (Game.Players.LocalPlayer.MembershipType ~= Enum.MembershipType.None)
local productAmount = nil
if isBcMember then
productAmount = getClosestRobuxProduct(amountNeededToBuy, bcRobuxProducts)
else
productAmount = getClosestRobuxProduct(amountNeededToBuy, nonBcRobuxProducts)
end
local isAndroid = false
pcall(function() isAndroid = (Game:GetService("UserInputService"):GetPlatform() == Enum.Platform.Android) end)
local prependString = ""
local appendString = ""
local appPrefix = ""
if isAndroid then
prependString = "robux"
if isBcMember then
appendString = "bc"
end
appPrefix = "com.roblox.client."
else
if isBcMember then
appendString = "RobuxBC"
else
appendString = "RobuxNonBC"
end
appPrefix = "com.roblox.robloxmobile."
end
local productString = appPrefix .. prependString .. tostring(productAmount) .. appendString
return productAmount, productString
end
function getClosestRobuxProductToBuyItem(productAmount, playerBalanceInRobux)
local amountNeededToBuy = productAmount - playerBalanceInRobux
local amountToBuy, productName = getMinimumProductNeededForPurchase(amountNeededToBuy)
local remainderAfterPurchase = amountToBuy - productAmount
return amountToBuy, remainderAfterPurchase, productName
end
function canUseNewRobuxToProductFlow()
return (Game:GetService("UserInputService").TouchEnabled and doNativePurchasing)
end
-- make sure our gui displays the proper purchase data, and set the productid we will try and buy if use specifies a buy action
function updatePurchasePromptData(insufficientFunds)
local newItemDescription = ""
-- id to use when we request a purchase
if not currentProductId then
currentProductId = currentProductInfo["ProductId"]
end
if isFreeItem() then
newItemDescription = string.gsub( freeItemPurchaseText,"itemName", tostring(currentProductInfo["Name"]))
newItemDescription = string.gsub( newItemDescription,"assetType", tostring(assetTypeToString(currentProductInfo["AssetTypeId"])) )
setHeaderText(takeHeaderText)
elseif insufficientFunds and canUseNewRobuxToProductFlow() then
local purchaseText = productPurchaseWithMoreRobuxText
local playerBalance = getPlayerBalance()
if not playerBalance then
newItemDescription = "Could not retrieve your balance. Please try again later."
elseif canUseNewRobuxToProductFlow() then
local amountToBuy, remainderAfterPurchase, productName = getClosestRobuxProductToBuyItem(currentCurrencyAmount, playerBalance["robux"])
thirdPartyProductName = productName
newItemDescription = string.gsub( purchaseText,"itemName", tostring(currentProductInfo["Name"]))
newItemDescription = string.gsub( newItemDescription,"assetType", tostring(assetTypeToString(currentProductInfo["AssetTypeId"])) )
newItemDescription = string.gsub( newItemDescription,"robuxToBuyAmount", tostring(amountToBuy))
if remainderAfterPurchase > 0 then
newItemDescription = newItemDescription .. " " .. string.gsub( productPurchaseWithMoreRobuxRemainderText,"purchaseRemainder", tostring(remainderAfterPurchase))
end
end
setHeaderText(buyHeaderText)
else
local purchaseText = productPurchaseText
if currentProductIsTixOnly() then
purchaseText = productPurchaseTixOnlyText
end
newItemDescription = string.gsub( purchaseText,"itemName", tostring(currentProductInfo["Name"]))
newItemDescription = string.gsub( newItemDescription,"assetType", tostring(assetTypeToString(currentProductInfo["AssetTypeId"])) )
newItemDescription = string.gsub( newItemDescription,"currencyType", tostring(currencyTypeToString(currentCurrencyType)) )
newItemDescription = string.gsub( newItemDescription,"currencyAmount", tostring(currentCurrencyAmount))
setHeaderText(buyHeaderText)
end
purchaseDialog.BodyFrame.ItemDescription.Text = newItemDescription
if purchasingConsumable then
purchaseDialog.BodyFrame.ItemPreview.Image = baseUrl .. "thumbs/asset.ashx?assetid=" .. tostring(currentProductInfo["IconImageAssetId"]) .. '&x=100&y=100&format=png'
else
purchaseDialog.BodyFrame.ItemPreview.Image = baseUrl .. "thumbs/asset.ashx?assetid=" .. tostring(currentAssetId) .. '&x=100&y=100&format=png'
end
end
function checkIfCanPurchase()
if checkingPlayerFunds then
local canPurchase, insufficientFunds, notRightBC = canPurchaseItem() -- check again to see if we can buy item
if not canPurchase or (insufficientFunds or notRightBC) then -- wait a bit and try a few more times
local retries = 20
while retries > 0 and (insufficientFunds or notRightBC) and checkingPlayerFunds and canPurchase do
wait(0.5)
canPurchase, insufficientFunds, notRightBC = canPurchaseItem()
retries = retries - 1
end
end
if canPurchase and not insufficientFunds then
-- we can buy item! set our buttons up and we will exit this loop
setButtonsVisible(purchaseDialog.BodyFrame.BuyButton,purchaseDialog.BodyFrame.CancelButton, purchaseDialog.BodyFrame.AfterBalanceText)
end
end
end
function closePurchasePrompt()
purchaseDialog:TweenPosition(hidePosition, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, tweenTime, true, function()
game.GuiService:RemoveCenterDialog(purchaseDialog)
hidePurchasing()
purchaseDialog.Visible = false
currentlyPrompting = false
currentlyPurchasing = false
Game:GetService("UserInputService").ModalEnabled = false
end)
end
function showPurchasePrompt()
local canPurchase, insufficientFunds, notRightBC, override, descText = canPurchaseItem()
if canPurchase then
updatePurchasePromptData(insufficientFunds)
if override and descText then
purchaseDialog.BodyFrame.ItemDescription.Text = descText
purchaseDialog.BodyFrame.AfterBalanceText.Visible = false
end
game.GuiService:AddCenterDialog(purchaseDialog, Enum.CenterDialogType.ModalDialog,
--ShowFunction
function()
-- set the state for our buttons
purchaseDialog.Visible = true
if not currentlyPurchasing then
if isFreeItem() then
setButtonsVisible(purchaseDialog.BodyFrame.FreeButton, purchaseDialog.BodyFrame.CancelButton)
elseif notRightBC then
setButtonsVisible(purchaseDialog.BodyFrame.BuyBCButton, purchaseDialog.BodyFrame.CancelButton)
elseif insufficientFunds then
setButtonsVisible(purchaseDialog.BodyFrame.BuyRobuxButton, purchaseDialog.BodyFrame.CancelButton)
elseif override then
if currentProductIsTixOnly() then
purchaseDialog.BodyFrame.AfterBalanceText.Visible = true
end
setButtonsVisible(purchaseDialog.BodyFrame.BuyDisabledButton, purchaseDialog.BodyFrame.CancelButton)
else
setButtonsVisible(purchaseDialog.BodyFrame.BuyButton, purchaseDialog.BodyFrame.CancelButton)
end
end
Game:GetService("UserInputService").ModalEnabled = true
purchaseDialog:TweenPosition(showPosition, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, tweenTime, true)
end,
--HideFunction
function()
Game:GetService("UserInputService").ModalEnabled = false
purchaseDialog.Visible = false
end)
else -- we failed in prompting a purchase, do a decline
doDeclinePurchase()
end
end
-- given an asset id, this function will grab that asset from the website, and return the first "Tool" object found inside it
function getToolAssetID(assetID)
local newTool = game:GetService("InsertService"):LoadAsset(assetID)
if not newTool then return nil end
if newTool:IsA("Tool") then
return newTool
end
local toolChildren = newTool:GetChildren()
for i = 1, #toolChildren do
if toolChildren[i]:IsA("Tool") then
return toolChildren[i]
end
end
return nil
end
-- the user tried to purchase by clicking the purchase button, but something went wrong.
-- let them know their account was not charged, and that they do not own the item yet.
function purchaseFailed(errorType)
local name = "Item"
if currentProductInfo then name = currentProductInfo["Name"] end
local newPurchasedFailedText = string.gsub( purchaseFailedText,"itemName", tostring(name))
if errorType == "inGamePurchasesDisabled" then
newPurchasedFailedText = string.gsub( newPurchasedFailedText,"errorReason", tostring(errorPurchasesDisabledText) )
elseif errorType == "didNotBuyRobux" then
newPurchasedFailedText = string.gsub( newPurchasedFailedText,"errorReason", tostring(errorPurchasesBuyRobuxText) )
else
newPurchasedFailedText = string.gsub( newPurchasedFailedText,"errorReason", tostring(errorPurchasesUnknownText) )
end
purchaseDialog.BodyFrame.ItemDescription.Text = newPurchasedFailedText
purchaseDialog.BodyFrame.ItemPreview.Image = errorImageUrl
purchaseDialog.BodyFrame.AfterBalanceText.Text = ""
setButtonsVisible(purchaseDialog.BodyFrame.OkButton)
setHeaderText(buyFailedHeaderText)
hidePurchasing()
end
-- user has specified they want to buy an item, now try to attempt to buy it for them
function doAcceptPurchase(currencyPreferredByUser)
if currentlyPurchasing then return end
currentlyPurchasing = true
showPurchasing() -- shows a purchasing ui (shows spinner)
local startTime = tick()
-- http call to do the purchase
local response = "none"
local url = nil
local flagExists, flagValue = pcall(function() return settings():GetFFlag("AddRequestIdToDeveloperProductPurchases") end)
local reliableDevProductEnabled = flagExists and flagValue
-- consumables need to use a different url
if purchasingConsumable then
url = getSecureApiBaseUrl() .. "marketplace/submitpurchase?productId=" .. tostring(currentProductId) ..
"&currencyTypeId=" .. tostring(currencyEnumToInt(currentCurrencyType)) ..
"&expectedUnitPrice=" .. tostring(currentCurrencyAmount) ..
"&placeId=" .. tostring(Game.PlaceId)
if reliableDevProductEnabled then
local h = game:GetService("HttpService")
url = url .. "&requestId=" .. h:UrlEncode(h:GenerateGUID(false))
end
else
url = getSecureApiBaseUrl() .. "marketplace/purchase?productId=" .. tostring(currentProductId) ..
"&currencyTypeId=" .. tostring(currencyEnumToInt(currentCurrencyType)) ..
"&purchasePrice=" .. tostring(currentCurrencyAmount) ..
"&locationType=Game" .. "&locationId=" .. Game.PlaceId
end
local success, reason = ypcall(function()
response = game:HttpPostAsync(url, "RobloxPurchaseRequest")
end)
if reliableDevProductEnabled and purchasingConsumable then
local retriesLeft = 3
local gotGoodResponse = success and response ~= "none" and response ~= nil and response ~= ''
while retriesLeft > 0 and (not gotGoodResponse) do
wait(1)
retriesLeft = retriesLeft - 1
success, reason = ypcall(function()
response = game:HttpPostAsync(url, "RobloxPurchaseRequest")
end)
gotGoodResponse = success and response ~= "none" and response ~= nil and response ~= ''
end
game:ReportInGoogleAnalytics("Developer Product", "Purchase",
gotGoodResponse and ("success. Retries = " .. (3 - retriesLeft)) or ("failure: " .. tostring(reason)), 1)
end
-- debug output for us (found in the logs from local)
print("doAcceptPurchase success from ypcall is ",success,"reason is",reason)
if (tick() - startTime) < 1 then
wait(1) -- allow the purchasing waiting dialog to at least be readable (otherwise it might flash, looks bad)...
end
-- check to make sure purchase actually happened on the web end
if response == "none" or response == nil or response == '' then
print("did not get a proper response from web on purchase of",currentAssetId,currentProductId)
purchaseFailed()
return
end
-- parse our response, decide how to react
response = getRbxUtility().DecodeJSON(response)
if response then
if response["success"] == false then
if response["status"] ~= "AlreadyOwned" then
print("web return response of fail on purchase of",currentAssetId,currentProductId)
if (response["status"] == "EconomyDisabled") then
purchaseFailed("inGamePurchasesDisabled")
else
purchaseFailed()
end
return
end
end
else
print("web return response of non parsable JSON on purchase of",currentAssetId)
purchaseFailed()
return
end
-- check to see if this item was bought, and if we want to equip it (also need to make sure the asset type was gear)
if currentEquipOnPurchase and success and currentAssetId and tonumber(currentProductInfo["AssetTypeId"]) == 19 then
local tool = getToolAssetID(tonumber(currentAssetId))
if tool then
tool.Parent = game.Players.LocalPlayer.Backpack
end
end
if purchasingConsumable then
if not response["receipt"] then
print("tried to buy productId, but no receipt returned. productId was",currentProductId)
purchaseFailed()
return
end
Game:GetService("MarketplaceService"):SignalClientPurchaseSuccess( tostring(response["receipt"]), game.Players.LocalPlayer.userId, currentProductId )
else
userPurchaseActionsEnded(success)
end
end
-- user pressed the cancel button, just remove all purchasing prompts
function doDeclinePurchase()
if currentlyPurchasing then return end
userPurchaseActionsEnded(false)
end
-------------------------------- End Accept/Decline Functions --------------------------------------
---------------------------------------------- Currency Functions ---------------------------------------------
-- enums have no implicit conversion to numbers in lua, has to have a function to do this
function currencyEnumToInt(currencyEnum)
if currencyEnum == Enum.CurrencyType.Robux or currencyEnum == Enum.CurrencyType.Default then
return 1
elseif currencyEnum == Enum.CurrencyType.Tix then
return 2
end
end
-- oi, this isn't so ugly anymore
function assetTypeToString(assetType)
local assetTypes = {
[1] = "Image";
[2] = "T-Shirt";
[3] = "Audio";
[4] = "Mesh";
[5] = "Lua";
[6] = "HTML";
[7] = "Text";
[8] = "Hat";
[9] = "Place";
[10] = "Model";
[11] = "Shirt";
[12] = "Pants";
[13] = "Decal";
[16] = "Avatar";
[17] = "Head";
[18] = "Face";
[19] = "Gear";
[21] = "Badge";
[22] = "Group Emblem";
[24] = "Animation";
[25] = "Arms";
[26] = "Legs";
[27] = "Torso";
[28] = "Right Arm";
[29] = "Left Arm";
[30] = "Left Leg";
[31] = "Right Leg";
[32] = "Package";
[33] = "YouTube Video";
[34] = "Game Pass";
[0] = "Product";
}
return assetTypes[assetType] or ""
end
function currencyTypeToString(currencyType)
if currencyType == Enum.CurrencyType.Tix then
return "Tix"
else
return "R$"
end
end
-- figure out what currency to use based on the currency you can actually sell the item in and what the script specified
function setCurrencyAmountAndType(priceInRobux, priceInTix)
if currentCurrencyType == Enum.CurrencyType.Default or currentCurrencyType == Enum.CurrencyType.Robux then -- sell for default (user doesn't care) or robux
if priceInRobux ~= nil and priceInRobux ~= 0 then -- we can sell for robux
currentCurrencyAmount = priceInRobux
currentCurrencyType = Enum.CurrencyType.Robux
else -- have to use tix
currentCurrencyAmount = priceInTix
currentCurrencyType = Enum.CurrencyType.Tix
end
elseif currentCurrencyType == Enum.CurrencyType.Tix then -- we want to sell for tix
if priceInTix ~= nil and priceInTix ~= 0 then -- we can sell for tix
currentCurrencyAmount = priceInTix
currentCurrencyType = Enum.CurrencyType.Tix
else -- have to use robux
currentCurrencyAmount = priceInRobux
currentCurrencyType = Enum.CurrencyType.Robux
end
else
return false
end
if currentCurrencyAmount == nil then
return false
end
return true
end
-- will get the player's balance of robux and tix, return in a table
function getPlayerBalance()
local playerBalance = nil
local success, errorCode = ypcall(function() playerBalance = game:HttpGetAsync(getSecureApiBaseUrl() .. "currency/balance") end)
if not success then
print("Get player balance failed because",errorCode)
return nil
end
if playerBalance == '' then
return nil
end
playerBalance = getRbxUtility().DecodeJSON(playerBalance)
return playerBalance
end
-- should open an external default browser window to this url
function openBuyCurrencyWindow()
checkingPlayerFunds = true
game:GetService("GuiService"):OpenBrowserWindow(baseUrl .. "Upgrades/Robux.aspx")
end
function buyEnoughCurrencyForProduct()
showPurchasing()
Game.MarketplaceService:PromptNativePurchase(Game.Players.LocalPlayer, thirdPartyProductName)
end
function openBCUpSellWindow()
checkingPlayerFunds = true
Game:GetService('GuiService'):OpenBrowserWindow(baseUrl .. "Upgrades/BuildersClubMemberships.aspx")
end
-- set up the gui text at the bottom of the prompt (alerts user to how much money they will have left, or if they need to buy more to buy the item)
function updateAfterBalanceText(playerBalance, notRightBc, balancePreText)
if isFreeItem() then
purchaseDialog.BodyFrame.AfterBalanceText.Text = freeItemBalanceText
return true, false
end
local keyWord = nil
if currentCurrencyType == Enum.CurrencyType.Robux then
keyWord = "robux"
elseif currentCurrencyType == Enum.CurrencyType.Tix then
keyWord = "tickets"
end
if not keyWord then
return false
end
local playerBalanceNumber = tonumber(playerBalance[keyWord])
if not playerBalanceNumber then
return false
end
local afterBalanceNumber = playerBalanceNumber - currentCurrencyAmount
-- check to see if we have enough of the desired currency to allow a purchase, if not we need to prompt user to buy robux
if not notRightBc then
if afterBalanceNumber < 0 and keyWord == "robux" then
purchaseDialog.BodyFrame.AfterBalanceText.Text = ""
return true, true
elseif afterBalanceNumber < 0 and keyWord == "tickets" then
purchaseDialog.BodyFrame.AfterBalanceText.Text = "You need " .. tostring(-afterBalanceNumber) .. " " .. currencyTypeToString(currentCurrencyType) .. " more to buy this item."
return true, true -- user can't buy more tickets, so we say fail the transaction (maybe instead we can prompt them to trade currency???)
end
else
purchaseDialog.BodyFrame.AfterBalanceText.Text = upgradeBCText
return true, false
end
if currentProductIsTixOnly() then
purchaseDialog.BodyFrame.AfterBalanceText.Text = tostring(balancePreText) .. tostring(afterBalanceNumber) .. " " .. currencyTypeToString(currentCurrencyType) .. "."
else
purchaseDialog.BodyFrame.AfterBalanceText.Text = tostring(balancePreText) .. currencyTypeToString(currentCurrencyType) .. tostring(afterBalanceNumber) .. "."
end
purchaseDialog.BodyFrame.AfterBalanceText.Visible = true
return true, false
end
function isFreeItem()
-- Apparently free items have 'IsForSale' set to false, but 'IsPublicDomain' set to true
-- Example: https://api.roblox.com/marketplace/productinfo?assetid=163811695
-- I've tested it, if you take it off the public domain, 'IsPublicDomain' is of course false
return currentProductInfo and currentProductInfo["IsPublicDomain"] == true
end
---------------------------------------------- End Currency Functions ---------------------------------------------
---------------------------------------------- Data Functions -----------------------------------------------------
-- more enum to int fun!
function membershipTypeToNumber(membership)
if membership == Enum.MembershipType.None then
return 0
elseif membership == Enum.MembershipType.BuildersClub then
return 1
elseif membership == Enum.MembershipType.TurboBuildersClub then
return 2
elseif membership == Enum.MembershipType.OutrageousBuildersClub then
return 3
end
return -1
end
function currentProductIsTixOnly()
local priceInRobux = currentProductInfo["PriceInRobux"]
local priceInTix = currentProductInfo["PriceInTickets"]
if priceInRobux == nil then return true end
priceInRobux = tonumber(priceInRobux)
if priceInRobux == nil then return true end
if priceInTix == nil then return false end
priceInTix = tonumber(priceInTix)
if priceInTix == nil then return false end
return (priceInRobux <= 0 and priceInTix > 0)
end
-- This functions checks to make sure the purchase is even possible, if not it returns false and we don't prompt user (some situations require user feedback when we won't prompt)
function canPurchaseItem()
-- first we see if player already owns the asset/get the productinfo
local playerOwnsAsset = false
local notRightBc = false
local descText = nil
local getProductSuccess = false
local getProductErrorReason = ""
if purchasingConsumable then
getProductSuccess, getProductErrorReason = pcall(function() currentProductInfo = game:GetService("MarketplaceService"):GetProductInfo(currentProductId, Enum.InfoType.Product) end)
else
getProductSuccess, getProductErrorReason = pcall(function() currentProductInfo = game:GetService("MarketplaceService"):GetProductInfo(currentAssetId) end)
end
if getProductSuccess == false or currentProductInfo == nil then
print("could not get product info because",getProductErrorReason)
descText = "In-game sales are temporarily disabled. Please try again later."
return false, nil, nil, true, descText
end
if not purchasingConsumable then
if not currentAssetId then
return false
end
if currentAssetId <= 0 then
return false
end
local success, errorCode = ypcall(function() playerOwnsAsset = game:HttpGetAsync(getSecureApiBaseUrl()
.. "ownership/hasAsset?userId="
.. tostring(game.Players.LocalPlayer.userId)
.. "&assetId=" .. tostring(currentAssetId))
end)
if not success then
return false
end
if playerOwnsAsset == true or playerOwnsAsset == "true" then
descText = "You already own this item."
return true, nil, nil, true, descText
end
end
-- For public models, as there is still the freeButton, indicating it should be
-- available, while this function (canPurchaseItem) doesn't count on free stuff
if isFreeItem() then
return true
end
purchaseDialog.BodyFrame.AfterBalanceText.Visible = true
-- next we parse through product info and see if we can purchase
if type(currentProductInfo) ~= "table" then
currentProductInfo = getRbxUtility().DecodeJSON(currentProductInfo)
end
if not currentProductInfo then
descText = "Could not get product info. Please try again later."
return true, nil, nil, true, descText
end
if currentProductInfo["IsForSale"] == false and currentProductInfo["IsPublicDomain"] == false then
descText = "This item is no longer for sale."
return true, nil, nil, true, descText
end
-- now we start talking money, making sure we are going to be able to purchase this
if not setCurrencyAmountAndType(tonumber(currentProductInfo["PriceInRobux"]), tonumber(currentProductInfo["PriceInTickets"])) then
descText = "We couldn't retrieve the price of the item correctly. Please try again later."
return true, nil, nil, true, descText
end
local playerBalance = getPlayerBalance()
if not playerBalance then
descText = "Could not retrieve your balance. Please try again later."
return true, nil, nil, true, descText
end
if tonumber(currentProductInfo["MinimumMembershipLevel"]) > membershipTypeToNumber(game.Players.LocalPlayer.MembershipType) then
notRightBc = true
end
local updatedBalance, insufficientFunds = updateAfterBalanceText(playerBalance, notRightBc, balanceFutureTenseText)
if notRightBc then
purchaseDialog.BodyFrame.AfterBalanceText.Active = true
return true, insufficientFunds, notRightBc, false
end
if currentProductInfo["ContentRatingTypeId"] == 1 then
if game.Players.LocalPlayer:GetUnder13() then
descText = "Your account is under 13 so purchase of this item is not allowed."
return true, nil, nil, true, descText
end
end
if (currentProductInfo["IsLimited"] == true or currentProductInfo["IsLimitedUnique"] == true) and
(currentProductInfo["Remaining"] == "" or currentProductInfo["Remaining"] == 0 or currentProductInfo["Remaining"] == nil) then
descText = "All copies of this item have been sold out! Try buying from other users on www.roblox.com."
return true, nil, nil, true, descText
end
if not updatedBalance then
descText = 'Could not update your balance. Please check back after some time.'
return true, nil, nil, true, descText
end
if insufficientFunds then
-- if this is a ticket only time and we don't have enough, tell the user to get more tix
if currentProductIsTixOnly() then
descText = "This item costs more tickets than you currently have! Try trading currency on www.roblox.com to get more tickets."
return true, nil, nil, true, descText
end
end
-- we use insufficient funds to display a prompt to buy more robux
return true, insufficientFunds
end
---------------------------------------------- End Data Functions -----------------------------------------------------
---------------------------------------------- Gui Functions ----------------------------------------------
function startSpinner()
if purchaseDialog.PurchasingFrame.Visible then return end
purchaseDialog.PurchasingFrame.Visible = true
renderSteppedConnection = Game:GetService("RunService").RenderStepped:connect(function()
purchaseDialog.PurchasingFrame.PurchasingSpinnerOuter.Rotation = purchaseDialog.PurchasingFrame.PurchasingSpinnerOuter.Rotation + 7
purchaseDialog.PurchasingFrame.PurchasingSpinnerInner.Rotation = purchaseDialog.PurchasingFrame.PurchasingSpinnerInner.Rotation - 9
end)
end
function stopSpinner()
if renderSteppedConnection then
renderSteppedConnection:disconnect()
renderSteppedConnection = nil
purchaseDialog.PurchasingFrame.Visible = false
end
end
-- next two functions control the "Purchasing..." overlay
function showPurchasing()
startSpinner()
end
function hidePurchasing()
stopSpinner()
end
-- convenience method to say exactly what buttons should be visible (all others are not!)
function setButtonsVisible(...)
local args = {...}
local argCount = select('#', ...)
local bodyFrameChildren = purchaseDialog.BodyFrame:GetChildren()
for i = 1, #bodyFrameChildren do
if bodyFrameChildren[i]:IsA("GuiButton") then
bodyFrameChildren[i].Visible = false
for j = 1, argCount do
if bodyFrameChildren[i] == args[j] then
bodyFrameChildren[i].Visible = true
break
end
end
end
end
end
-- all the gui init. Would be nice if this didn't have to be a script
function createPurchasePromptGui()
purchaseDialog = Instance.new("Frame")
purchaseDialog.Name = "PurchaseFrame"
purchaseDialog.Size = UDim2.new(0,435,0,292)
purchaseDialog.Position = hidePosition
purchaseDialog.Active = true
purchaseDialog.Visible = false
purchaseDialog.BackgroundColor3 = Color3.new(225/255,225/255,225/255)
purchaseDialog.BorderSizePixel = 0
purchaseDialog.ZIndex = 8
purchaseDialog.Parent = game.CoreGui.RobloxGui
local bodyFrame = Instance.new("Frame")
bodyFrame.Name = "BodyFrame"
bodyFrame.Active = true
bodyFrame.Size = UDim2.new(1,-10,1,-55)
bodyFrame.Position = UDim2.new(0,5,0,50)
bodyFrame.BackgroundColor3 = Color3.new(1, 1, 1)
bodyFrame.BorderSizePixel = 0
bodyFrame.ZIndex = 8
bodyFrame.Parent = purchaseDialog
local titleLabel = createTextObject("TitleLabel", "Buy Item", "TextLabel", Enum.FontSize.Size36)
titleLabel.Active = true
titleLabel.Font = Enum.Font.SourceSansBold
titleLabel.TextColor3 = Color3.new(54/255,54/255,54/255)
titleLabel.ZIndex = 8
titleLabel.Size = UDim2.new(1,0,0,50)
titleLabel.Parent = purchaseDialog
local distanceBetweenButtons = 20
local cancelButton = createImageButton("CancelButton")
cancelButton.Position = UDim2.new(0.5,(distanceBetweenButtons/2),1,-100)
cancelButton.BorderColor3 = Color3.new(86/255,86/255,86/255)
cancelButton.Parent = bodyFrame
cancelButton.Modal = true
cancelButton.ZIndex = 8
cancelButton.Image = cancelButtonImageUrl
cancelButton.MouseButton1Up:connect(function( )
cancelButton.Image = cancelButtonImageUrl
end)
cancelButton.MouseLeave:connect(function( )
cancelButton.Image = cancelButtonImageUrl
end)
cancelButton.MouseButton1Click:connect(doDeclinePurchase)
local cancelText = createTextObject("CancelText","Cancel","TextLabel",Enum.FontSize.Size24)
cancelText.TextColor3 = Color3.new(1,1,1)
cancelText.Size = UDim2.new(1,0,1,0)
cancelText.ZIndex = 8
cancelText.Parent = cancelButton
local cancelHoverFrame = Instance.new("Frame")
cancelHoverFrame.Name = "HoverFrame"
cancelHoverFrame.Size = UDim2.new(1,0,1,0)
cancelHoverFrame.BackgroundColor3 = Color3.new(1,1,1)
cancelHoverFrame.BackgroundTransparency = 0.7
cancelHoverFrame.BorderSizePixel = 0
cancelHoverFrame.Visible = false
cancelHoverFrame.ZIndex = 8
cancelHoverFrame.Parent = cancelButton
cancelButton.MouseEnter:connect(function()
cancelHoverFrame.Visible = true
end)
cancelButton.MouseLeave:connect(function( )
cancelHoverFrame.Visible = false
end)
cancelButton.MouseButton1Click:connect(function( )
cancelHoverFrame.Visible = false
end)
local buyButton = createImageButton("BuyButton")
buyButton.Position = UDim2.new(0.5,-117-(distanceBetweenButtons/2),1,-100)
buyButton.BorderColor3 = Color3.new(0,112/255,1/255)
buyButton.Image = buyImageUrl
buyButton.ZIndex = 8
buyButton.Parent = bodyFrame
local buyText = createTextObject("BuyText","Buy Now","TextLabel",Enum.FontSize.Size24)
buyText.ZIndex = 8
buyText.TextColor3 = Color3.new(1,1,1)
buyText.Size = UDim2.new(1,0,1,0)
buyText.Parent = buyButton
local buyHoverFrame = cancelHoverFrame:Clone()
buyButton.MouseEnter:connect(function()
buyHoverFrame.Visible = true
end)
buyButton.MouseLeave:connect(function( )
buyHoverFrame.Visible = false
end)
buyButton.MouseButton1Click:connect(function( )
buyHoverFrame.Visible = false
end)
buyHoverFrame.Parent = buyButton
local buyDisabledButton = buyButton:Clone()
buyDisabledButton.Name = "BuyDisabledButton"
buyDisabledButton.AutoButtonColor = false
buyDisabledButton.Visible = false
buyDisabledButton.Active = false
buyDisabledButton.Parent = bodyFrame
local buyRobux = buyButton:Clone()
buyRobux.Name = "BuyRobuxButton"
buyRobux.AutoButtonColor = false
buyRobux.Visible = false
buyRobux.ZIndex = 8
if canUseNewRobuxToProductFlow() then
buyRobux.BuyText.Text = "Buy"
else
buyRobux.BuyText.Text = "Buy R$"
end
buyRobux.MouseEnter:connect(function()
buyRobux.HoverFrame.Visible = true
end)
buyRobux.MouseLeave:connect(function( )
buyRobux.HoverFrame.Visible = false
end)
buyRobux.MouseButton1Click:connect(function( )
buyRobux.HoverFrame.Visible = false
if canUseNewRobuxToProductFlow() then
buyEnoughCurrencyForProduct()
else
openBuyCurrencyWindow()
end
end)
buyRobux.Parent = bodyFrame
local buyBC = buyRobux:Clone()
buyBC.Name = "BuyBCButton"
buyBC.BuyText.Text = "Buy Builders Club"
buyBC.MouseEnter:connect(function()
buyBC.HoverFrame.Visible = true
end)
buyBC.MouseLeave:connect(function( )
buyBC.HoverFrame.Visible = false
end)
buyBC.MouseButton1Click:connect(function( )
buyBC.HoverFrame.Visible = false
openBCUpSellWindow()
end)
buyBC.Parent = bodyFrame
local freeButton = buyButton:Clone()
freeButton.BuyText.Text = "Take Free"
freeButton.BackgroundTransparency = 1
freeButton.Name = "FreeButton"
freeButton.Visible = false
freeButton.MouseEnter:connect(function()
freeButton.HoverFrame.Visible = true
end)
freeButton.MouseButton1Click:connect(function( )
freeButton.HoverFrame.Visible = false
end)
freeButton.MouseLeave:connect(function( )
freeButton.HoverFrame.Visible = false
end)
freeButton.Parent = bodyFrame
local okButton = buyButton:Clone()
okButton.BuyText.Text = "Ok"
okButton.Name = "OkButton"
okButton.Visible = false
okButton.Position = UDim2.new(0.5,-okButton.Size.X.Offset/2,1,-100)
okButton.Modal = true
okButton.MouseEnter:connect(function()
okButton.HoverFrame.Visible = true
end)
okButton.MouseButton1Click:connect(function( )
okButton.HoverFrame.Visible = false
signalPromptEnded(false)
end)
okButton.MouseLeave:connect(function( )
okButton.HoverFrame.Visible = false
end)
okButton.Parent = bodyFrame
local okPurchasedButton = okButton:Clone()
okPurchasedButton.Name = "OkPurchasedButton"
okPurchasedButton.MouseEnter:connect(function()
okPurchasedButton.HoverFrame.Visible = true
end)
okPurchasedButton.MouseLeave:connect(function( )
okPurchasedButton.HoverFrame.Visible = false
end)
okPurchasedButton.MouseButton1Click:connect(function()
okPurchasedButton.HoverFrame.Visible = false
if purchasingConsumable then
userPurchaseProductActionsEnded(true)
else
signalPromptEnded(true)
end
end)
okPurchasedButton.Parent = bodyFrame
buyButton.MouseButton1Click:connect(function() doAcceptPurchase(Enum.CurrencyType.Robux) end)
freeButton.MouseButton1Click:connect(function() doAcceptPurchase(false) end)
local itemPreview = Instance.new("ImageLabel")
itemPreview.Name = "ItemPreview"
itemPreview.BackgroundTransparency = 1
itemPreview.BorderSizePixel = 0
itemPreview.Position = UDim2.new(0,20,0,20)
itemPreview.Size = UDim2.new(0,100,0,100)
itemPreview.ZIndex = 9
itemPreview.Parent = bodyFrame
local itemDescription = createTextObject("ItemDescription","","TextLabel",Enum.FontSize.Size18)
itemDescription.TextXAlignment = Enum.TextXAlignment.Left
itemDescription.Position = UDim2.new(0.5, -70, 0, 10)
itemDescription.Size = UDim2.new(0,245,0,115)
itemDescription.TextColor3 = Color3.new(54/255,54/255,54/255)
itemDescription.ZIndex = 8
itemDescription.Parent = bodyFrame
local afterBalanceText = createTextObject("AfterBalanceText","","TextLabel",Enum.FontSize.Size14)
afterBalanceText.BackgroundTransparency = 1
afterBalanceText.TextColor3 = Color3.new(102/255,102/255,102/255)
afterBalanceText.Position = UDim2.new(0,5,1,-33)
afterBalanceText.Size = UDim2.new(1,-10,0,28)
afterBalanceText.ZIndex = 8
afterBalanceText.Parent = bodyFrame
local purchasingFrame = Instance.new("Frame")
purchasingFrame.Name = "PurchasingFrame"
purchasingFrame.Size = UDim2.new(1,0,1,0)
purchasingFrame.BackgroundColor3 = Color3.new(0,0,0)
purchasingFrame.BackgroundTransparency = 0.05
purchasingFrame.BorderSizePixel = 0
purchasingFrame.ZIndex = 9
purchasingFrame.Visible = false
purchasingFrame.Active = true
purchasingFrame.Parent = purchaseDialog
local purchasingLabel = createTextObject("PurchasingLabel","Purchasing","TextLabel",Enum.FontSize.Size48)
purchasingLabel.Size = UDim2.new(1,0,1,0)
purchasingLabel.Position = UDim2.new(0,0,0,-24)
purchasingLabel.ZIndex = 10
purchasingLabel.Parent = purchasingFrame
local purchasingSpinner = Instance.new("ImageLabel")
purchasingSpinner.Name = "PurchasingSpinnerOuter"
purchasingSpinner.Image = loadingImage
purchasingSpinner.BackgroundTransparency = 1
purchasingSpinner.BorderSizePixel = 0
purchasingSpinner.Size = UDim2.new(0,64,0,64)
purchasingSpinner.Position = UDim2.new(0.5,-32,0.5,32)
purchasingSpinner.ZIndex = 10
purchasingSpinner.Parent = purchasingFrame
local purchasingSpinnerInner = purchasingSpinner:Clone()
purchasingSpinnerInner.BackgroundTransparency = 1
purchasingSpinnerInner.Name = "PurchasingSpinnerInner"
purchasingSpinnerInner.Size = UDim2.new(0,32,0,32)
purchasingSpinnerInner.Position = UDim2.new(0.5,-16,0.5,48)
purchasingSpinnerInner.Parent = purchasingFrame
end
-- next 2 functions are convenienvce creation functions for guis
function createTextObject(name, text, type, size)
local textLabel = Instance.new(type)
textLabel.Font = Enum.Font.SourceSans
textLabel.TextColor3 = Color3.new(217/255, 217/255, 217/255)
textLabel.TextWrapped = true
textLabel.Name = name
textLabel.Text = text
textLabel.BackgroundTransparency = 1
textLabel.BorderSizePixel = 0
textLabel.FontSize = size
return textLabel
end
function createImageButton(name)
local imageButton = Instance.new("ImageButton")
imageButton.Size = UDim2.new(0,117,0,60)
imageButton.Name = name
return imageButton
end
function setHeaderText(text)
purchaseDialog.TitleLabel.Text = text
end
function doPurchasePrompt(player, assetId, equipIfPurchased, currencyType, productId)
if not purchaseDialog then
createPurchasePromptGui()
end
if player == game.Players.LocalPlayer then
if currentlyPrompting then return end
currentlyPrompting = true
currentAssetId = assetId
currentProductId = productId
currentCurrencyType = currencyType
currentEquipOnPurchase = equipIfPurchased
purchasingConsumable = (currentProductId ~= nil)
showPurchasePrompt()
end
end
function userPurchaseProductActionsEnded(userIsClosingDialog)
checkingPlayerFunds = false
if userIsClosingDialog then
closePurchasePrompt()
if currentServerResponseTable then
local isPurchased = false
if tostring(currentServerResponseTable["isValid"]):lower() == "true" then
isPurchased = true
end
Game:GetService("MarketplaceService"):SignalPromptProductPurchaseFinished(tonumber(currentServerResponseTable["playerId"]), tonumber(currentServerResponseTable["productId"]), isPurchased)
end
removeCurrentPurchaseInfo()
else
if tostring(currentServerResponseTable["isValid"]):lower() == "true" then
local newPurchasedSucceededText = string.gsub( purchaseSucceededText,"itemName", tostring(currentProductInfo["Name"]))
purchaseDialog.BodyFrame.ItemDescription.Text = newPurchasedSucceededText
local playerBalance = getPlayerBalance()
local keyWord = "robux"
if currentCurrencyType == Enum.CurrencyType.Tix then
keyWord = "tickets"
end
local afterBalanceNumber = playerBalance[keyWord]
purchaseDialog.BodyFrame.AfterBalanceText.Text = tostring(balanceCurrentTenseText) .. currencyTypeToString(currentCurrencyType) .. tostring(afterBalanceNumber) .. "."
setButtonsVisible(purchaseDialog.BodyFrame.OkPurchasedButton)
hidePurchasing()
else
purchaseFailed()
end
end
end
function doProcessServerPurchaseResponse(serverResponseTable)
if not serverResponseTable then
purchaseFailed()
return
end
if serverResponseTable["playerId"] and tonumber(serverResponseTable["playerId"]) == game.Players.LocalPlayer.userId then
currentServerResponseTable = serverResponseTable
userPurchaseProductActionsEnded(false)
end
end
---------------------------------------------- End Gui Functions ----------------------------------------------
---------------------------------------------- Script Event start/initialization ----------------------------------------------
preloadAssets()
game:GetService("MarketplaceService").PromptProductPurchaseRequested:connect(function(player, productId, equipIfPurchased, currencyType)
doPurchasePrompt(player, nil, equipIfPurchased, currencyType, productId)
end)
Game:GetService("MarketplaceService").PromptPurchaseRequested:connect(function(player, assetId, equipIfPurchased, currencyType)
doPurchasePrompt(player, assetId, equipIfPurchased, currencyType, nil)
end)
Game:GetService("MarketplaceService").ServerPurchaseVerification:connect(function(serverResponseTable)
doProcessServerPurchaseResponse(serverResponseTable)
end)
Game:GetService("GuiService").BrowserWindowClosed:connect(checkIfCanPurchase)
if not canUseNewRobuxToProductFlow() then return end
Game.MarketplaceService.NativePurchaseFinished:connect(function(player, productId, wasPurchased)
if wasPurchased then
-- try for 20 seconds to see if we get the funds if we purchased something
local retriesLeft = 40
local canPurchase, insufficientFunds, notRightBC = canPurchaseItem()
while canPurchase and insufficientFunds and retriesLeft > 0 do
wait(0.5)
canPurchase, insufficientFunds, notRightBC = canPurchaseItem()
retriesLeft = retriesLeft - 1
end
if canPurchase and not insufficientFunds and not notRightBC then
doAcceptPurchase(Enum.CurrencyType.Robux)
else
purchaseFailed("didNotBuyRobux")
end
else
purchaseFailed("didNotBuyRobux")
end
end)