mcpField
R
roblox-monetization

roblox-monetization

github.com/el4cteo开发56496 次安装2 周前更新
代码仓库

关于 roblox-monetization

面向 Roblox 商店与购买流程开发者的变现指南,说明商品类型、服务器端发放、收据处理、定价、退款及合规检查。

主要功能

  1. 01商品类型选择指南
  2. 02幂等收据处理
  3. 03通行证与订阅处理
  4. 04实时定价与商品发现
  5. 05退款与拒付处理
  6. 06政策和地区检查

适用场景

  1. 01安全发放开发者商品购买内容
  2. 02实现可撤销的订阅权益
  3. 03显示当前地区价格
  4. 04退款后撤销已发放权益

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/el4cteo/roblox-skills --skill roblox-monetization

安装后在 Claude Code 中输入/roblox-monetization确认已加载。

附带文件references/ads-and-extras.md

详细介绍

Roblox 变现

产品类型

类型购买方式用途发放方式
开发者产品可重复购买货币、消耗品、增益、复活MarketplaceService.ProcessReceipt(服务器)
通行证一次购买,永久有效VIP、永久特权、额外栏位加入时检查 UserOwnsGamePassAsync + 购买完成事件
订阅按月订阅,可撤销定期特权(日常货币、VIP)GetUserSubscriptionStatusAsync + UserSubscriptionStatusChanged
激励视频广告免费(观看广告)小额奖励(价值约 3–10 Robux)通过 ProcessReceipt 发放的开发者产品
商业产品真实货币(实物商品)附带数字权益的周边商品CommerceService;数字权益通过 ProcessReceipt 发放
私人服务器按月订阅付费私人服务器在 Creator Dashboard 中配置

所有奖励都必须在服务器上发放。切勿通过 PromptProductPurchaseFinished 等客户端事件发放奖励——客户端可以伪造这些事件。

开发者产品:ProcessReceipt

规则:

  • 只能有一个服务器脚本设置 MarketplaceService.ProcessReceipt。
  • 只有在奖励已持久化后,才返回 PurchaseGranted。否则返回 NotProcessedYet; Roblox 会稍后重试(包括玩家下次加入时),并使用相同的 PurchaseId。
  • 处理程序必须具备幂等性:在玩家保存的数据中记录已处理的 PurchaseId,确保重试时 不会重复发放奖励。
  • 收据可能会在玩家的数据尚未加载或玩家已经离开时到达:请等待其数据加载,或 返回 NotProcessedYet。
--!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

使用 ProfileStore 时,“已持久化”是指等待 PurchaseId 出现在 profile.LastSavedData 中(或调用 profile:Save() 并等待);请参阅 ProfileStore 的开发者产品指南。

从客户端(UI)提示购买——奖励仍由服务器发放: MarketplaceService:PromptProductPurchase(Players.LocalPlayer, productId)。

通行证

--!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 的结果会按服务器缓存;购买完成处理程序会处理会话期间的购买。

订阅

  • 加入时以及收到 Players.UserSubscriptionStatusChanged(player, subscriptionId) 时,通过 MarketplaceService:GetUserSubscriptionStatusAsync(player, "EXP-...") 检查订阅状态 → { IsSubscribed, IsRenewing }。
  • 权益必须可撤销:当 IsSubscribed 为 false 时,移除已持久化的特权。
  • 使用 MarketplaceService:PromptSubscriptionPurchase(player, subscriptionId) 提示购买。
  • 将通行证替换为订阅时:继续兑现现有通行证持有者的权益,并下架该通行证。

销售商品的商店

  • 使用 MarketplaceService:RankProductsAsync(identifiers) 个性化排序(加入时调用一次—— 速率限制严格);使用 RecommendTopProductsAsync({ Enum.InfoType.Product, Enum.InfoType.GamePass }) 显示“热门推荐” (需要在过去 28 天内有过销售;请在 task.spawn 中调用)。
  • 使用 GetProductInfoAsync(id, Enum.InfoType.Product) / GetDeveloperProductsAsync() 显示实时价格——切勿 将 Robux 价格硬编码:动态定价(区域定价 + 价格优化)会向不同 用户显示不同价格。receipt.CurrencySpent 表示用户实际支付的金额。
  • 情境化优惠(死亡时提供复活、卡住时提供增益)比静态菜单效果更好。也请在 Creator Hub 中游戏的商店选项卡列出商品。
  • 使用 MarketplaceService:OpenShop(player),从自定义按钮、NPC 或区域打开 Roblox 商店叠加层。
  • 已上架的通行证和开发者产品也可以在游戏外销售(主页、搜索、Robux 购买后);玩家加入时,商品已经购买。开发者产品只有正确设置 ProcessReceipt 才符合资格——Creator Hub → Monetization → Shop 会标记需要修复的产品。

退款和拒付

已退款的通行证(以及头像物品)会自动失去所有权——请重新检查所有权,而不是永久缓存。开发者产品的发放记录保存在你的数据中,因此你需要自行撤销:订阅 Transaction Refunded webhook(涵盖开发者产品、通行证、私人服务、付费访问),并根据 payload 中的交易 ID/收据 ID 进行匹配(参见 roblox-open-cloud)。将已处理的 PurchaseId 与每项发放内容一并保存,以便在退款时撤销。

合规

  • 付费随机物品(战利品箱、扭蛋、使用 Robux 或用 Robux 购买的货币购买的抽奖):购买前必须披露概率,并检查 PolicyService:GetPolicyInfoForPlayerAsync(player):
    • ArePaidRandomItemsRestricted → 提供免费获取途径、已披露的固定序列,或直接购买选项。
    • IsPaidItemTradingAllowed → 如果值为 false,则禁用付费物品交易。
  • 区域定价套利:使用 MarketplaceService:GetUsersPriceLevelsAsync(userIds)(1–1000,即全球价格的百分比)限制已购物品的赠送/交易;在玩家加入时获取,不要跨会话缓存。
  • 激励视频广告的奖励必须是开发者产品,不得随机发放;广告播放期间不得伤害玩家角色。
  • 遵守 PolicyService 返回的广告、社交链接和交易年龄/区域限制。

激励视频广告、商业产品、Premium/Roblox Plus 挂钩、美国 18+ DevEx 汇率及年龄检查: references/ads-and-extras.md。

相关技能

roblox-data-stores(持久化发放记录)、roblox-security(经济系统漏洞)、roblox-ui(商店 UI)、roblox-analytics-liveops(经济事件、漏斗、实验)。

评论

支持 Markdown