Code directly from the profileservice docs
Output:
My code:
local SETTINGS = {
ProfileTemplate = {
Cash = 0,
},
Products = { -- developer_product_id = function(profile)
[1203567724] = function(profile)
profile.Data.Cash += 10
print(profile.Data.Cash)
end,
[1203567938] = function(profile)
profile.Data.Cash += 100
print(profile.Data.Cash)
end,
},
PurchaseIdLog = 50, -- Store this amount of purchase id's in MetaTags;
-- This value must be reasonably big enough so the player would not be able
-- to purchase products faster than individual purchases can be confirmed.
-- Anything beyond 30 should be good enough.
}
----- Loaded Modules -----
local ProfileService = require(game.ServerScriptService.ProfileService)
----- Private Variables -----
local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
local GameProfileStore = ProfileService.GetProfileStore(
"PlayerData",
SETTINGS.ProfileTemplate
)
local Profiles = {} -- {player = profile, ...}
----- Private Functions -----
local function PlayerAdded(player)
local profile = GameProfileStore:LoadProfileAsync("Player_" .. player.UserId)
if profile ~= nil then
profile:AddUserId(player.UserId) -- GDPR compliance
profile:Reconcile() -- Fill in missing variables from ProfileTemplate (optional)
profile:ListenToRelease(function()
Profiles[player] = nil
player:Kick() -- The profile could've been loaded on another Roblox server
end)
if player:IsDescendantOf(Players) == true then
Profiles[player] = profile
else
profile:Release() -- Player left before the profile loaded
end
else
-- The profile couldn't be loaded possibly due to other
-- Roblox servers trying to load this profile at the same time:
player:Kick()
end
end
function PurchaseIdCheckAsync(profile, purchase_id, grant_product_callback) --> Enum.ProductPurchaseDecision
-- Yields until the purchase_id is confirmed to be saved to the profile or the profile is released
if profile:IsActive() ~= true then
return Enum.ProductPurchaseDecision.NotProcessedYet
else
local meta_data = profile.MetaData
local local_purchase_ids = meta_data.MetaTags.ProfilePurchaseIds
if local_purchase_ids == nil then
local_purchase_ids = {}
meta_data.MetaTags.ProfilePurchaseIds = local_purchase_ids
end
-- Granting product if not received:
if table.find(local_purchase_ids, purchase_id) == nil then
while #local_purchase_ids >= SETTINGS.PurchaseIdLog do
table.remove(local_purchase_ids, 1)
end
table.insert(local_purchase_ids, purchase_id)
coroutine.wrap(grant_product_callback)()
end
-- Waiting until the purchase is confirmed to be saved:
local result = nil
local function check_latest_meta_tags()
local saved_purchase_ids = meta_data.MetaTagsLatest.ProfilePurchaseIds
if saved_purchase_ids ~= nil and table.find(saved_purchase_ids, purchase_id) ~= nil then
result = Enum.ProductPurchaseDecision.PurchaseGranted
end
end
check_latest_meta_tags()
local meta_tags_connection = profile.MetaTagsUpdated:Connect(function()
check_latest_meta_tags()
-- When MetaTagsUpdated fires after profile release:
if profile:IsActive() == false and result == nil then
result = Enum.ProductPurchaseDecision.NotProcessedYet
end
end)
while result == nil do
task.wait()
end
meta_tags_connection:Disconnect()
return result
end
end
local function GetPlayerProfileAsync(player) --> [Profile] / nil
-- Yields until a Profile linked to a player is loaded or the player leaves
local profile = Profiles[player]
while profile == nil and player:IsDescendantOf(Players) == true do
task.wait()
profile = Profiles[player]
end
return profile
end
local function GrantProduct(player, product_id)
-- We shouldn't yield during the product granting process!
local profile = Profiles[player]
local product_function = SETTINGS.Products[product_id]
if product_function ~= nil then
product_function(profile)
else
warn("ProductId " .. tostring(product_id) .. " has not been defined in Products table")
end
end
local function ProcessReceipt(receipt_info)
local player = Players:GetPlayerByUserId(receipt_info.PlayerId)
if player == nil then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local profile = GetPlayerProfileAsync(player)
if profile ~= nil then
return PurchaseIdCheckAsync(
profile,
receipt_info.PurchaseId,
function()
GrantProduct(player, receipt_info.ProductId)
end
)
else
return Enum.ProductPurchaseDecision.NotProcessedYet
end
end
----- Initialize -----
for _, player in ipairs(Players:GetPlayers()) do
coroutine.wrap(PlayerAdded)(player)
end
MarketplaceService.ProcessReceipt = ProcessReceipt
----- Connections -----
Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(function(player)
local profile = Profiles[player]
if profile ~= nil then
profile:Release()
end
end)
Help?
@loleris