roblox-monetization について
Roblox のショップや購入フローを構築する開発者向けの収益化ガイドです。商品タイプ、サーバー側の付与、レシート処理、価格、返金、コンプライアンス確認を説明します。
主な機能
- 01商品タイプの選択ガイド
- 02冪等なレシート処理
- 03パスとサブスクリプションの処理
- 04現在価格と商品の表示
- 05返金とチャージバックへの対応
- 06ポリシーと地域の確認
活用シーン
- 01開発者プロダクトの購入分を安全に付与
- 02取り消し可能なサブスクリプション特典の実装
- 03現在の地域別価格の表示
- 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(サーバー) |
| Pass | 1回、永続 | 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(経済イベント、ファネル、実験)。