👨‍💻

【Roblox】開発者製品の購入について

2024/09/18に公開

はじめに

今回は開発者製品の購入のプログラムについて紹介します。

Robloxバージョン:0.638.1.6380615

参考にした記事・サイト
https://create.roblox.com/docs/production/monetization/developer-products
https://create.roblox.com/docs/reference/engine/classes/MarketplaceService

購入ダイアログを出す

MarketplaceService:PromptProductPurchase(localPlayer, assetId)

MarketplaceServiceのPromptProductPurchaseを呼ぶことで開発者製品の購入ダイアログを作成できます。

引数
localPlayer:ダイアログを出したいPlayer
assetId:購入させたいアセットのId


購入ダイアログ

上記ダイアログにある値段や名前、アイコンなどはRobloxのダッシュボードで設定したものが表示されます。

プログラム上で変更や編集はできないので注意が必要です。

※編集はダッシュボードでできます。

購入ダイアログを閉じた

-- 購入ダイアログを閉じた際に呼ばれる
local function onPromptProductPurchaseFinished(userId:number, productId:number, isPurchased:boolean)
	local player = game:GetService("Players"):GetPlayerByUserId(userId)

	if isPurchased then
		-- 購入に成功した場合
		print("Success to purchase", player, productId)
	else
		-- 購入に失敗した場合
		error("Failure to purchase", player, productId)
	end
end

MarketplaceService.PromptProductPurchaseFinished:Connect(onPromptProductPurchaseFinished)

MarketplaceServiceのPromptProductPurchaseFinishedは購入ダイアログを閉じた際に呼ばれる関数を設定できるイベントです。

登録したメソッドの引数
userId:購入ダイアログを閉じたPlayerのユーザーId
productId:開発者製品のアセットId
isPurchased:購入されたかどうか(キャンセルでダイアログを閉じた場合はfalse)

注意点はダイアログが閉じた際に開発者製品の効果を付与するのは遅いということです。
このタイミングでDataStoreに課金情報を保存する場合、ダイアログを閉じずにゲームから退出したユーザーには効果が付与されない可能性があります。

購入後の効果の付与は次の項目で説明します。

購入後の結果の付与

-- 開発者製品の購入確認処理
local function processReceipt(receiptInfo)
	return Enum.ProductPurchaseDecision.PurchaseGranted
end

-- 開発者製品 購入時に呼ばれる。
MarketplaceService.ProcessReceipt = processReceipt

MarketplaceServiceのProcessReceiptコールバックに関数を設定することで開発者製品が購入された後すぐにこのメソッドが呼ばれます。
購入後の効果の反映はここで行います。(コインの追加や攻撃力の追加など)

引数
receiptInfo:
このコールバックに渡されるreceiptInfoテーブルには、次のデータが含まれます。
PurchaseId — 特定の購入の一意の識別子。(ランダムに割り振られた商品ごとの固有のId)
PlayerId — 購入したプレイヤーのユーザー ID。
ProductId — 購入した製品の ID。(アセットId)
PlaceIdWherePurchased — 購入が行われた場所の ID。必ずしも現在の場所の ID と同じではありません。
CurrencySpent — トランザクションで費やされた通貨の金額。
CurrencyType — 購入に使用された通貨の種類。常にEnum.CurrencyType.Robux です。

注意点

設定したメソッドはEnum.ProductPurchaseDecisionを返す必要があります。
これが返されることで購入が完了or失敗したとみなされるためです。

MarketplaceService.ProcessReceiptはイベントではないため、1つしか設定できません。
ほかのScriptからも設定すると予期せぬ挙動になるようです。

開発者製品はパスと違い、ユーザーが購入したかどうかを自分(製作者)が管理しないといけません。
また、このコールバックはユーザーが未解決の購入があった場合に、再度サーバーに入った時に呼ばれる場合があります。
⇒ DataStoreを用いてユーザーに対して効果を付与済みかどうかを保存する必要があります。

-- 開発者製品を保存する用のデータストア
local DataStoreService = game:GetService("DataStoreService")
local purchaseHistoryStore = DataStoreService:GetDataStore("PurchaseHistory")

-- 開発者製品の購入確認処理
local function processReceipt(receiptInfo)
	-- すでに購入済みかどうかをDataStoreから確認
	local playerProductKey = receiptInfo.PlayerId .. "_" .. receiptInfo.PurchaseId
	local purchased = false
	local success, errorMessage

	success, errorMessage = pcall(function()
		purchased = purchaseHistoryStore:GetAsync(playerProductKey)
	end)

	if success and purchased then
		-- すでに効果付与済み
		return Enum.ProductPurchaseDecision.PurchaseGranted
	elseif not success then
		-- DataStoreでエラーが出た
		error("Data store error:" .. errorMessage)
	end

	-- DataStoreに効果付与の状況を保存する
	local playerProductKey = receiptInfo.PlayerId .. "_" .. receiptInfo.PurchaseId
	local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
	local success, isPurchaseRecorded = pcall(function()
		return purchaseHistoryStore:UpdateAsync(playerProductKey, function(alreadyPurchased)
			if alreadyPurchased then
				-- すでに効果付与済み
				return true
			end
			if not player then
				-- このサーバーにプレイヤーがいない
				return nil
			end

			-- 任意の効果付与の関数を実行する
			local handler = productPurchaseHandler
			local success, result = pcall(handler, receiptInfo.PlayerId, receiptInfo.ProductId, true)
			if not success or not result then
				-- 効果付与失敗
				warn("Failed to process a product purchase for ProductId: " .. tostring(receiptInfo.ProductId) .. " Player: " .. tostring(player) .. " Error: " .. tostring(result))
				return nil
			end
			-- データストアに記録する
			return true
		end)
	end)

	-- 効果付与が完了した場合のみ、購入完了とみなす。
	if not success then
		warn("Failed to process receipt due to data store error.")
		return Enum.ProductPurchaseDecision.NotProcessedYet
	elseif isPurchaseRecorded == nil then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	else	
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end
end

-- 開発者製品 購入時に呼ばれる。
MarketplaceService.ProcessReceipt = processReceipt

DataStoreに付与済みか確認、Playerがサーバーにいれば効果を付与、結果を保存、購入完了という流れです。

もし細かく開発者製品の購入情報を詳しく保持したければ、DataStoreに保存する内容を増やすことも可能です。

また、こういったユーザーのデータに直接かかわる処理はサーバー側で行ってください。クライアント側で行うと悪用される可能性があります。

まとめ

  • 購入のダイアログを出す処理とダイアログを閉じた時に呼ばれる処理はゲームパスと関数名が違うだけでほぼ同じ処理。
  • 開発者製品の購入効果の反映はMarketplaceService.ProcessReceiptコールバックを使用する。
  • MarketplaceService.ProcessReceiptの結果はDataStoreに保存する。

公式フォーラムでは、開発者製品の処理は開発者によって自由に作成できるという意見もあります。
自分が作成した処理は一例なのでゲームに沿ったよりよい処理を作成しましょう。
ぜひMarketplaceServiceを使いこなしましょう!!

ランド・ホー Roblox開発チーム

Discussion