New Merch Booth Developer Module

Not speaking as a UGC creator, but isn’t this just reinforcing the already insane 40% affiliate fee? If this is widely adopted, the Catalog might be scarcely used since the Catalog is already outdated and cluttered, making things almost impossible to find since there isn’t proper tag metadata support. UGC creators already have to compete with popular games, this will make it even harder now to compete with all of the games that will utilize this since they can just easily paste in the asset link. I understand this can greatly increase net profits since there’ll be a general increase of transactions but the revenue balance between creators and sellers is way too unfavorable to creators.

On top of the current UGC options, UGC creators should have an option to only allow game sales from a specified list as well as an option to disallow sales within the Catalog itself. This could create exclusivity allowing UGC creators to incentivize purchasing assets outside of the catalog or in their own experiences so they may keep the revenue which would otherwise be going to someone who just pasted the asset link into a module and can generally help with engagement and exposure to other associated products. This would give many UGC creators an advantage in the game development space which I would assume many of the leading developers already qualify for UGC creation. This would also incentivize the UGC creators to not only create assets but to create these marketplaces, whether that be game-integrated shops, in-game purchasable rewards, games exclusively dedicated to creating a marketplace/shopping experience, etc. Purchasable rewards are something that I feel has been long overdue. Imagine being able to buy a hat for your avatar that only 10% of the most dedicated players in [insert game here] have. It’d be a monumentally better version of having a rare trophy since you could show off your achievements through your avatar with exclusive and themed accessories.

I really don’t think it’s fair to creators that spend hundreds or even thousands of hours creating assets only to get a huge amount of their revenue cut from front-page games (if they have All Games sales enabled), and now with the promotion of this module, random less popular games on top of the already encroaching cut from Roblox themselves.

Personally I believe for items sold in-game, UGC creators should make 50% and the affiliate should make 20%. (Currently UGC creators make a measly 30%, while affiliates make 40% just for copy+pasting the asset link into their game.)

I realize the enormous affiliate fee is probably the way of reinforcing and incentivizing all leagues of developers to make games that generate extremely favorable amounts of revenue but the way it is currently, it’s basically fighting and splitting that revenue between two very different types of developers (one side of which I personally don’t think deserves such a high cut) all while Roblox makes more money in the bottom line from the increase of transactions across the board.

13 Likes

This module’s tutorial and relevant information can be found here.
https://create.roblox.com/docs/tools/modules/merch-booth

5 Likes

This looks inspired from avatar editor service

4 Likes

This opens the door for many scams.

You can copy the UI, make like a limited hat and sell it for a few thousand sayings its a sale etc. You can hook up with your own gamepass and make thousands off it.

4 Likes

You can do this without the help of this module, I’d even argue that you can find a free model like this in the Roblox toolbox and do whatever malicious thing you’d want to.

This isn’t opening the door at all, it’s already been opened. Also just for reference, limiteds can’t be sold in places. Making a gamepass, making it look like a new limited and having someone gullible enough to buy it is already rare, plus you’ll get terminated for it.

3 Likes

I think maybe that I will just have some idea

1 Like

This is a great resource to help developers monetize their experiences! I actually added it into one of my experiences already to try it out!

A few thoughts on this module:

Feature Request: Add a function to open the catalog to a subset of items
It would be helpful if the catalog could be opened to a subset of the items available in the catalog. This idea essentially combines the concept of the openItemView function with the catalog filtering functionality.

Possible use cases:

  • When a player interacts with an ATM, open the catalog to the “Cash” developer products rather than to all the developer products / the entire catalog.
  • When a player inspects a mannequin’s outfit, open the catalog to only the items displayed on the mannequin.

Two implementation ideas for how the items to display could be specified:

  1. Pass an itemId array to the function.
  2. Allow the developer to tag an itemId with a “category”. To open to a subset of items, the developer would call the function with the desired category name. Note: If this option is implemented, it would be beneficial to allow an itemId to be present in multiple categories at the same time (e.g. the developer may wish to use the same item in multiple outfits).

Bug: The removeItem function does not remove the specified item
The removeItem function does not actually remove the specified item from the catalog. After digging into the code a little bit, I found that the client-side implementation was incomplete (the code needed in the setEnabled module was missing). Here is the setEnabled module code with the finished implementation:

local LocalizationService = game:GetService("LocalizationService")
local UserInputService = game:GetService("UserInputService")
local MarketplaceService = game:GetService("MarketplaceService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local MerchBooth = script:FindFirstAncestor("MerchBooth")

local Roact = require(MerchBooth.Packages["Roact_1.4.3"])
local RoactRodux = require(MerchBooth.Packages["RoactRodux_0.5.0"])
local t = require(MerchBooth.Packages["t_3.0.0"])
local App = require(MerchBooth.Components.App)
local getCurrentDevice = require(MerchBooth.Modules.getCurrentDevice)
local uiStatesEvents = require(MerchBooth.Api.uiStatesEvents)
local itemEvents = require(MerchBooth.Api.itemEvents)
local reducer = require(MerchBooth.Reducers.reducer)
local setMerchBoothVisible = require(MerchBooth.Actions.setMerchBoothVisible)
local setItemInfo = require(MerchBooth.Actions.setItemInfo)
local removeItemInfo = require(MerchBooth.Actions.removeItemInfo)
local setMerchBoothEnabled = require(MerchBooth.Actions.setMerchBoothEnabled)
local updateItemInfo = require(MerchBooth.Actions.updateItemInfo)
local addProximityButton = require(MerchBooth.Actions.addProximityButton)
local removeProximityButton = require(MerchBooth.Actions.removeProximityButton)
local setDevice = require(MerchBooth.Actions.setDevice)

local initialLoadData = MerchBooth.Remote.InitialLoadData :: RemoteEvent

return function(store: any)
	local LocalPlayer = Players.LocalPlayer

	local app = Roact.createElement(RoactRodux.StoreProvider, {
		store = store,
	}, {
		Roact.createElement(App),
	})

	local mountedTree
	local connections = {}

	local function onItemInfoReceived(id: string, info: table)
		store:dispatch(setItemInfo(id, info))

		local isOwned = false
		if info.productType == Enum.InfoType.Asset then
			isOwned = MarketplaceService:PlayerOwnsAsset(LocalPlayer, id) -- yields
		elseif info.productType == Enum.InfoType.GamePass then
			isOwned = MarketplaceService:UserOwnsGamePassAsync(LocalPlayer.UserId, id)
		end
		store:dispatch(updateItemInfo(id, {
			isOwned = isOwned,
		}))

		-- Titles and descriptions are in EN since they come from the server. If locale is non-EN, they need to be
		-- fetched again in order to be translated
		if LocalizationService.RobloxLocaleId ~= "en-us" then
			task.spawn(function()
				local updatedInfo = MarketplaceService:GetProductInfo(tonumber(id), Enum.InfoType.Asset) -- yields
				store:dispatch(updateItemInfo(id, {
					title = updatedInfo.Name,
					description = updatedInfo.Description,
				}))
			end)
		end
	end

	local function setCurrentDevice()
		local device = getCurrentDevice(UserInputService:GetLastInputType())
		store:dispatch(setDevice(device))
	end

	--[=[
		Sets whether the entire MerchBooth is enabled (catalog view + item details + button + proximity buttons) or not.

		```lua
		local ReplicatedStorage = game:GetService("ReplicatedStorage")

		local MerchBooth = require(ReplicatedStorage:WaitForChild("MerchBooth"))

		MerchBooth.setEnabled(true)
		```

		@within MerchBooth
		@client
	]=]
	local function setEnabled(
		isEnabled: boolean,
		setItemInfoRemote: RemoteEvent?,
		removeItemInfoRemote: RemoteEvent?,
		addProximityButtonRemote: RemoteEvent?,
		removeProximityButtonRemote: RemoteEvent?,
		playParticleEmitterRemote: RemoteEvent?
	)
		assert(RunService:IsClient(), "MerchBooth.setEnabled must be called on the client")
		assert(t.boolean(isEnabled), "Bad argument #1 to MerchBooth.setEnabled: expected a boolean")

		store:dispatch(setMerchBoothEnabled(isEnabled))

		if isEnabled then
			connections.uiStateEvents = uiStatesEvents.connect(store)

			connections.itemEvents = itemEvents.connect(store, function(state: reducer.State)
				return state.server.itemInfo
			end)

			-- Mount MerchBoothUI to PlayerGui
			local playerGui = LocalPlayer:WaitForChild("PlayerGui")
			mountedTree = Roact.mount(app, playerGui)

			-- Setting up connections
			setItemInfoRemote = setItemInfoRemote or MerchBooth.Remote.SetItemInfo
			removeItemInfoRemote = removeItemInfoRemote or MerchBooth.Remote.RemoveItemInfo
			addProximityButtonRemote = addProximityButtonRemote or MerchBooth.Remote.AddProximityButton
			removeProximityButtonRemote = removeProximityButtonRemote or MerchBooth.Remote.RemoveProximityButton
			playParticleEmitterRemote = playParticleEmitterRemote or MerchBooth.Remote.PlayParticleEmitter

			connections.setItemInfoConnection = setItemInfoRemote.OnClientEvent:Connect(function(action)
				onItemInfoReceived(action.itemId, action.info)
			end)

			connections.removeItemInfoConnection = removeItemInfoRemote.OnClientEvent:Connect(function(action)
				store:dispatch(removeItemInfo(action.itemId))
			end)

			connections.addProximityConnection = addProximityButtonRemote.OnClientEvent:Connect(function(id, adornee)
				store:dispatch(addProximityButton(id, adornee))
			end)

			connections.removeProximityConnection = removeProximityButtonRemote.OnClientEvent:Connect(function(adornee)
				store:dispatch(removeProximityButton(adornee))
			end)

			connections.initialLoadEventConnection = initialLoadData.OnClientEvent:Connect(
				function(itemInfo, proximityButtons)
					for id, info in pairs(itemInfo) do
						onItemInfoReceived(id, info)
					end
					for _, tuple in ipairs(proximityButtons) do
						store:dispatch(addProximityButton(tuple[2], tuple[1]))
					end
				end
			)

			connections.characterAddedConnection = LocalPlayer.CharacterAdded:Connect(function()
				store:dispatch(setMerchBoothVisible(false))
			end)

			connections.playParticleEmitterConnection = playParticleEmitterRemote.OnClientEvent:Connect(function()
				local state: reducer.State = store:getState()
				local emitter = state.config.particleEmitterTemplate:Clone()
				emitter.Parent = LocalPlayer.Character.Humanoid.RootPart
				emitter:Emit(15)

				task.wait()
				if emitter.Parent then
					emitter.Enabled = false
				end

				task.wait(4)
				if emitter.Parent then
					emitter:Destroy()
				end
			end)

			connections.lastInputTypeChangedConnection = UserInputService.LastInputTypeChanged:Connect(setCurrentDevice)

			setCurrentDevice()
		else
			-- Unmount MerchBoothUI from PlayerGui
			if mountedTree then
				store:dispatch(setMerchBoothVisible(false))
				store:flush()
				Roact.unmount(mountedTree)
				mountedTree = nil
			end

			-- Disconnecting connections
			for _, connection in pairs(connections) do
				if typeof(connection) == "table" then
					-- The event returned by uiStateEvents uses a lowercase syntax
					connection:disconnect()
				else
					connection:Disconnect()
				end
			end
		end
	end

	return setEnabled
end

6 Likes

this is ‘official’ though - as it’ll be used in many games people may think it is safe.

1 Like

I think that maybe more help at the script

1 Like

I don’t think you understood my reply. Roblox official (official library release, not official substitute for the catalog) or not, anyone can create a scam interface to trick players. This release isn’t opening doors to any new scams.

Any player who wants to scam with a fake catalog can do so and could have been doing it long before this was released. You’re putting blame on the wrong people, this module isn’t causing any more damage.

2 Likes

It’s not an update is a free model Roblox released.

1 Like

Hi,
This module looks amazing and I have been able to add it to our new project. I would like to ask about the proximity prompt. I’m successfully adding products as in the tutorial, but if I modify the merchbooth.additemasync as indicated in Merch Booth | Roblox Creator Documentation , I get several errors:


An example code looks like this:


local ReplicatedStorage = game:GetService("ReplicatedStorage")

local MerchBooth = require(ReplicatedStorage:WaitForChild("MerchBooth"))

local success, errorMessage = pcall(function()
	MerchBooth.addItemAsync(4819740796)
end)
if success then
	local item = workspace:FindFirstChild("Robox")
	if item then
		MerchBooth.addProximityButton(item, 4819740796)
	end
end
2 Likes

In fact, These errors correspond to the proximity trying to be added to a regular part, they need to be added to an adornee (Surface GUI element). I managed to add one. thanks

2 Likes

Maybe think not too script error and or I know let’s me get just see how to get much Fix bug

2 Likes

This seems very useful. I really like the UI, It looks very clean.

2 Likes

Is there a way to view the amount of revenue generated from using the merch booth module?

3 Likes

This is a very interesting idea, we can look into this!

4 Likes

Hello developer I need some help I wanna ask you something removed Roblox system from finally gets fixed that server not getting issues I am so happy that you know

2 Likes

Absolutely love this Developer Module! Love how you can easily add any of your custom Gamepasses and items to the shop!

One feature that we would love added, currently with the Proximity Prompts implementation in the Developer Module, you can only call MerchBooth.addProximityButton(item, assetID) only once per asset id, meaning you can only add one proxmimity prompt per assetId.

I’ve tested this and when you attempt to call to add more than one proximity prompt per assetId, it will sort of just choose the first part when it is added. It would be awesome if functionality could be added to allow us to use add multiple proximity prompts for the same assetId! (In our case, our team has multiple shops all over the map, and we can only use a proximity prompt per assetId, in only one shop :sweat_smile:)

Some other users may want the same functionality so we can sell our shop items in multiple locations in our experiences! Other than that I really love this resource, can’t wait to see what other Developer Modules come out in the future! :smile:

2 Likes

Hi, there is no specific discussion and feedback thread for this module like all the others in the private category.

1 Like