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 の収益化

商品タイプ

タイプ購入方法用途付与方法
Developer product繰り返し通貨、消耗品、ブースト、復活MarketplaceService.ProcessReceipt(サーバー)
Pass1回、永続VIP、永続特典、追加スロット参加時にUserOwnsGamePassAsyncで確認+購入完了イベント
Subscription毎月、取り消し可能継続的な特典(日次通貨、VIP)GetUserSubscriptionStatusAsync+UserSubscriptionStatusChanged
Rewarded video ad無料(広告を視聴)少額の報酬(約3~10 Robux相当)ProcessReceiptを通じて付与するDeveloper product
Commerce product実際のお金(物理的な商品)デジタル特典が付属するグッズCommerceService。デジタル特典はProcessReceiptを通じて付与
Private server毎月有料のプライベートサーバーCreator Dashboardで設定

すべての付与処理はサーバーで行います。PromptProductPurchaseFinishedのようなクライアントイベントから付与してはいけません。クライアントはイベントを偽装できます。

Developer product:ProcessReceipt

ルール:

  • MarketplaceService.ProcessReceiptを設定するサーバースクリプトは、必ず1つだけにします。
  • 付与内容が永続化された後に限り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のdeveloper productガイドを参照してください。

クライアント(UI)からプロンプトを表示します。付与処理は引き続きサーバーで行います。 MarketplaceService:PromptProductPurchase(Players.LocalPlayer, productId)。

Pass

--!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の結果はサーバーごとにキャッシュされます。購入完了ハンドラーが、セッション中の購入に対応します。

Subscription

  • 参加時とPlayers.UserSubscriptionStatusChanged(player, subscriptionId)で、MarketplaceService:GetUserSubscriptionStatusAsync(player, "EXP-...")を使って状態を確認します。結果は{ IsSubscribed, IsRenewing }です。
  • 特典は取り消し可能にする必要があります。IsSubscribedがfalseの場合は、永続化した特典を削除します。
  • MarketplaceService:PromptSubscriptionPurchase(player, subscriptionId)でプロンプトを表示します。
  • PassをSubscriptionに置き換える場合は、既存のPass所有者には引き続き特典を提供し、Passの販売を停止します。

販売につながるショップ

  • MarketplaceService:RankProductsAsync(identifiers)で表示順をパーソナライズします(参加時に1回だけ呼び出します。レート制限が厳格です)。「おすすめ商品」はRecommendTopProductsAsync({ Enum.InfoType.Product, Enum.InfoType.GamePass })で表示します(過去28日以内に販売実績が必要です。task.spawn内で呼び出します)。
  • GetProductInfoAsync(id, Enum.InfoType.Product)/GetDeveloperProductsAsync()で最新の価格を表示します。Robux価格をハードコードしてはいけません。managed pricing(地域別価格設定+価格最適化)により、ユーザーごとに異なる価格が表示されます。receipt.CurrencySpentには、ユーザーが実際に支払った金額が記録されます。
  • 状況に応じたオファー(死亡時の復活、行き詰まったときのブースト)は、静的なメニューより効果的です。ゲームのCreator HubにあるShopタブにもアイテムを掲載します。
  • 独自のボタン、NPC、ゾーンからMarketplaceService:OpenShop(player)を使ってRobloxのShopオーバーレイを開きます。
  • 掲載済みのPassとdeveloper productは、ゲーム外(Home、検索、Robux購入後)でも販売できます。プレイヤーは商品をすでに購入した状態でゲームに参加します。developer productが対象となるのは、ProcessReceiptが正しく実装されている場合のみです。Creator Hub → Monetization → Shopには、修正が必要な商品が表示されます。

返金とチャージバック

返金されたパス(およびアバターアイテム)の所有権は自動的に失われます。所有権をキャッシュしたままにせず、再確認してください。 開発者プロダクトの付与情報はデータ内に保存されるため、自分で取り消す必要があります。Transaction Refunded ウェブフックを購読し(開発者プロダクト、パス、プライベートサーバー、Paid Access が対象)、ペイロード内のトランザクション 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(経済イベント、ファネル、実験)。

コメント