el4cteo/roblox-skills

roblox-monetization

Robux monetization - developer products with idempotent ProcessReceipt, passes, subscriptions, rewarded video ads, personalized shops, regional pricing and trade arbitrage, paid random item policy (PolicyService), commerce products, private servers, Roblox…

Ver código-fonte
Documento original do Skill

Renderizado do repositório de origem, preservando títulos, exemplos, código, tabelas, links e imagens.

Roblox monetization

Product types

TypeBuyUse forGrant via
Developer productRepeatedlyCurrency, consumables, boosts, revivesMarketplaceService.ProcessReceipt (server)
PassOnce, permanentVIP, permanent perks, extra slotsUserOwnsGamePassAsync check on join + purchase-finished event
SubscriptionMonthly, revocableRecurring perks (daily currency, VIP)GetUserSubscriptionStatusAsync + UserSubscriptionStatusChanged
Rewarded video adFree (watch ad)Small rewards (≈3–10 Robux value)Developer product granted through ProcessReceipt
Commerce productReal money (physical goods)Merch with bundled digital benefitsCommerceService; digital benefits via ProcessReceipt
Private serverMonthlyPaid private serversConfigured in Creator Dashboard

All granting happens on the server. Never grant from client events like PromptProductPurchaseFinished — clients can fake them.

Developer products: ProcessReceipt

Rules:

  • Exactly one server script sets MarketplaceService.ProcessReceipt.
  • Return PurchaseGranted only after the grant is persisted. Otherwise return NotProcessedYet;

Roblox retries later (including on the player's next join) with the same PurchaseId.

  • Handlers must be idempotent: record handled PurchaseIds in the player's saved data so a retry

never grants twice.

  • Receipts arrive for players who may not be loaded yet or may have left: wait for their data, or

return NotProcessedYet.

luau
--!strict
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")

type PlayerData = { coins: number, purchaseIds: { string } }
type ReceiptInfo = {
	PlayerId: number,
	ProductId: number,
	PurchaseId: string,
	CurrencySpent: number,
	CurrencyType: Enum.CurrencyType,
	PlaceIdWherePurchased: number,
}

local MAX_REMEMBERED_PURCHASES = 100

-- Provided by your data layer (ProfileStore profile.Data, etc.).
local function getLoadedData(_player: Player): PlayerData?
	return nil
end
local function saveNow(_player: Player): boolean
	return true
end

local PRODUCTS: { [number]: (Player, PlayerData) -> () } = {
	[1234567] = function(_player, data) -- 100 coins
		data.coins += 100
	end,
}

local function processReceipt(receipt: ReceiptInfo): Enum.ProductPurchaseDecision
	local player = Players:GetPlayerByUserId(receipt.PlayerId)
	if not player then
		return Enum.ProductPurchaseDecision.NotProcessedYet -- granted on their next visit
	end

	local data = getLoadedData(player)
	local deadline = os.clock() + 20
	while not data and player.Parent == Players and os.clock() < deadline do
		task.wait(0.5)
		data = getLoadedData(player)
	end
	if not data then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	if table.find(data.purchaseIds, receipt.PurchaseId) then
		return Enum.ProductPurchaseDecision.PurchaseGranted -- already granted earlier
	end

	local handler = PRODUCTS[receipt.ProductId]
	if not handler then
		warn(`No handler for product {receipt.ProductId}`)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	local ok, err = pcall(handler, player, data)
	if not ok then
		warn(`Grant failed for {receipt.PurchaseId}: {err}`)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	table.insert(data.purchaseIds, receipt.PurchaseId)
	while #data.purchaseIds > MAX_REMEMBERED_PURCHASES do
		table.remove(data.purchaseIds, 1)
	end

	if not saveNow(player) then
		return Enum.ProductPurchaseDecision.NotProcessedYet -- the idempotency list prevents a double grant
	end
	return Enum.ProductPurchaseDecision.PurchaseGranted
end

MarketplaceService.ProcessReceipt = processReceipt :: any

With ProfileStore, "persisted" means waiting until the PurchaseId appears in profile.LastSavedData (or calling profile:Save() and waiting); see ProfileStore's developer products guide.

Prompt from the client (UI) — the server still grants: MarketplaceService:PromptProductPurchase(Players.LocalPlayer, productId).

Passes

luau
--!strict
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")

local VIP_PASS_ID = 987654

local function grantVip(player: Player)
	player:SetAttribute("VIP", true) -- apply perks from one place
end

local function checkPass(player: Player)
	local ok, owns = pcall(MarketplaceService.UserOwnsGamePassAsync, MarketplaceService, player.UserId, VIP_PASS_ID)
	if ok and owns then
		grantVip(player)
	end
end

Players.PlayerAdded:Connect(checkPass)
for _, player in Players:GetPlayers() do
	task.spawn(checkPass, player)
end

-- Fires on the server too; re-verify ownership rather than trusting `purchased` alone.
MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player, passId, purchased)
	if purchased and passId == VIP_PASS_ID then
		checkPass(player)
	end
end)

UserOwnsGamePassAsync results are cached per server; the purchase-finished handler covers in-session buys.

Subscriptions

  • Check with MarketplaceService:GetUserSubscriptionStatusAsync(player, "EXP-...") → { IsSubscribed, IsRenewing }

on join and on Players.UserSubscriptionStatusChanged(player, subscriptionId).

  • Benefits must be revocable: when IsSubscribed is false, remove perks you persisted.
  • Prompt with MarketplaceService:PromptSubscriptionPurchase(player, subscriptionId).
  • Replacing a pass with a subscription: keep honoring existing pass owners, take the pass off sale.

Shops that sell

  • Personalize order with MarketplaceService:RankProductsAsync(identifiers) (call once on join —

strict rate limit) and "Top picks" with RecommendTopProductsAsync({ Enum.InfoType.Product, Enum.InfoType.GamePass }) (needs a sale in the last 28 days; call in task.spawn).

  • Show live prices from GetProductInfoAsync(id, Enum.InfoType.Product) / GetDeveloperProductsAsync() — never

hardcode Robux prices: managed pricing (regional pricing + price optimization) shows different users different prices. receipt.CurrencySpent has what the user actually paid.

  • Contextual offers (revive on death, boost when stuck) outperform static menus. Also list items in

the game's Shop tab in Creator Hub.

Compliance

  • Paid random items (loot boxes, gacha, spins bought with Robux or Robux-bought currency): disclose

odds before purchase, and check PolicyService:GetPolicyInfoForPlayerAsync(player):

  • ArePaidRandomItemsRestricted → offer a free earnable path, a disclosed fixed sequence, or direct purchase.
  • IsPaidItemTradingAllowed → disable trading of paid items when false.
  • Regional pricing arbitrage: gate gifting/trading of purchased items using

MarketplaceService:GetUsersPriceLevelsAsync(userIds) (1–1000, % of global price); fetch at join, don't cache across sessions.

  • Rewarded video rewards must be developer products, not random, and must not harm the player's

character while the ad plays.

  • Respect age/region restrictions returned by PolicyService for ads, social links, and trading.

Rewarded video ads, commerce products, Premium/Roblox Plus hooks: references/ads-and-extras.md.

Related skills

roblox-data-stores (persisting grants), roblox-security (economy exploits), roblox-ui (shop UI), roblox-analytics-liveops (economy events, funnels, experiments).

do mesmo repositório

Mais Skills

Todos os Skills
el4cteo
Comunidade

roblox-security

Anti-exploit design - never trust the client, validate every remote argument (NaN, spoofed instances, strings), rate limiting, ProximityPrompt/ClickDetector abuse, network ownership, movement and hit validation, economy/trade duplication, code confidentiality, secure teleports, heuristics, Ban API. Use when writing server handlers for client input, combat/economy/trading, or reviewing for exploits.

instalações
2
GitHub Stars
29
Atualizado
28 de set.
el4cteo
Comunidade

roblox-analytics-liveops

Analytics and live ops - AnalyticsService economy/funnel/onboarding/progression/custom events, player segments, experience configs via ConfigService, A/B experiments, badges, experience notifications, timed events, daily rewards, season passes. Use when instrumenting a game, tuning economy or onboarding, running A/B tests or feature flags, or planning live events and re-engagement.

instalações
2
GitHub Stars
29
Atualizado
28 de set.
el4cteo
Comunidade

roblox-architecture

Roblox project structure and client/server model - replication, where code and assets belong (ServerScriptService, ReplicatedStorage, StarterPlayer), RunContext, service/controller bootstrapping, player/character lifecycle, CollectionService components, attributes, streaming-safe client code, library choices. Use when starting a project, deciding where code goes, or fixing replication/streaming bugs.

instalações
2
GitHub Stars
29
Atualizado
28 de set.
el4cteo
Comunidade

roblox-audio

Sound and music - the Audio API (AudioPlayer, AudioEmitter, AudioListener, AudioDeviceOutput, Wire, effects, faders), classic Sound/SoundGroup, 2D vs 3D audio, music and SFX volume, client vs server playback, asset permissions, streaming caveats, voice routing, text-to-speech. Use when adding music, sound effects, spatial audio, mixing, or fixing sounds that don't play.

instalações
2
GitHub Stars
29
Atualizado
28 de set.