MarketplaceService

Show Deprecated
not creatable
service

MarketplaceService is responsible for in-experience transactions. The most notable methods are PromptProductPurchase and PromptPurchase, as well as the callback ProcessReceipt which must be defined so that transactions do not fail.

MarketplaceService also has methods that fetch information about developer products (GetProductInfo and GetDeveloperProductsAsync), passes (UserOwnsGamePassAsync), and other assets (PlayerOwnsAsset, PlayerOwnsBundle).

Understanding MarketplaceService is the first step towards learning to monetize an experience on Roblox, as well as DataStoreService which is responsible for saving and loading data related to purchases.

Summary

Methods

Events

Properties

Methods

PromptBundlePurchase

void

Prompts a user to purchase a bundle with the given bundleId.

Parameters

player: Instance
bundleId: number

Returns

void

PromptGamePassPurchase

void

Prompts a user to purchase a pass with the given gamePassId.

Parameters

player: Instance
gamePassId: number

Returns

void

PromptPremiumPurchase

void

Prompts a user to purchase Roblox Premium. To learn more about and incorporating Premium incentives into your experience, see Engagement-Based Payouts.

See Also

Parameters

player: Instance

The player being prompted to purchase Premium.


Returns

void

Code Samples

Prompt Premium Purchase

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local teleporter = script.Parent
local showModal = true
local TELEPORT_POSITION = Vector3.new(1200, 200, 60)
-- Teleport character to exclusive area
local function teleportPlayer(player)
-- Request streaming around target location
player:RequestStreamAroundAsync(TELEPORT_POSITION)
-- Teleport character
local character = player.Character
if character and character.Parent then
local currentPivot = character:GetPivot()
character:PivotTo(currentPivot * CFrame.new(TELEPORT_POSITION))
end
end
-- Detect character parts touching teleporter
teleporter.Touched:Connect(function(otherPart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if not player then return end
if not player:GetAttribute("CharacterPartsTouching") then
player:SetAttribute("CharacterPartsTouching", 0)
end
player:SetAttribute("CharacterPartsTouching", player:GetAttribute("CharacterPartsTouching") + 1)
if player.MembershipType == Enum.MembershipType.Premium then
-- User has Premium; teleport character to exclusive area within experience
teleportPlayer(player)
else
-- Show purchase modal, using debounce to show once every few seconds at most
if not showModal then return end
showModal = false
task.delay(5, function()
showModal = true
end)
MarketplaceService:PromptPremiumPurchase(player)
end
end)
-- Detect character parts exiting teleporter
teleporter.TouchEnded:Connect(function(otherPart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if player and player:GetAttribute("CharacterPartsTouching") then
player:SetAttribute("CharacterPartsTouching", player:GetAttribute("CharacterPartsTouching") - 1)
end
end)
-- Handle membership changed event
Players.PlayerMembershipChanged:Connect(function(player)
warn("User membership changed; new membership is " .. tostring(player.MembershipType))
-- Teleport character if membership type is Premium and character is on teleporter
if player.MembershipType == Enum.MembershipType.Premium and player:GetAttribute("CharacterPartsTouching") > 0 then
teleportPlayer(player)
end
end)

PromptProductPurchase

void

Prompts a user to purchase a developer product with the given productId.

Parameters

player: Instance
productId: number
equipIfPurchased: bool
Default Value: true
currencyType: Enum.CurrencyType
Default Value: "Default"

Returns

void

Code Samples

MarketplaceService:PromptProductPurchase

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local productId = 0000000 -- Change this to your developer product ID
-- Function to prompt purchase of the developer product
local function promptPurchase()
MarketplaceService:PromptProductPurchase(player, productId)
end
promptPurchase()

PromptPurchase

void

Prompts a user to purchase an item with the given assetId. This does not work for USD Creator Store purchases. If prompting a purchase of a Limited item:

  • Server requests (recommended) prompt original copies until they run out, regardless of the price. Once original copies are out, resale copies are prompted.

  • Client requests prompt from the lowest resale price even if original copies are available.

Parameters

player: Instance
assetId: number
equipIfPurchased: bool
Default Value: true
currencyType: Enum.CurrencyType

Ignored.

Default Value: "Default"

Returns

void

Code Samples

LocalScript (Client)

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local promptPurchaseEvent = ReplicatedStorage:WaitForChild("PromptPurchaseEvent")
local part = Instance.new("Part")
part.Parent = workspace
local clickDetector = Instance.new("ClickDetector")
clickDetector.Parent = part
clickDetector.MouseClick:Connect(function()
promptPurchaseEvent:FireServer(16630147)
end)
Script (Server)

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MarketplaceService = game:GetService("MarketplaceService")
local promptPurchaseEvent = Instance.new("RemoteEvent")
promptPurchaseEvent.Name = "PromptPurchaseEvent"
promptPurchaseEvent.Parent = ReplicatedStorage
-- Listen for the RemoteEvent to fire from a Client and then trigger the purchase prompt
promptPurchaseEvent.OnServerEvent:Connect(function(player, id)
MarketplaceService:PromptPurchase(player, id)
end)

PromptSubscriptionPurchase

void

Prompts a user to purchase a subscription for the given subscriptionId.

Parameters

user: Player

The Player object to be prompted to subscribe.

subscriptionId: string

The ID of the subscription to subscribe to.


Returns

void

GetDeveloperProductsAsync

yields

Returns a Pages object which contains information for all of the current experience's developer products.


Returns

Code Samples

MarketplaceService:GetDeveloperProductsAsync

local MarketplaceService = game:GetService("MarketplaceService")
local developerProducts = MarketplaceService:GetDeveloperProductsAsync():GetCurrentPage()
for _, developerProduct in pairs(developerProducts) do
for field, value in pairs(developerProduct) do
print(field .. ": " .. value)
end
print(" ")
end

GetProductInfo

yields

This method provides information about an asset, developer product, or pass given its asset ID and the Enum.InfoType. If no such item exists with the given ID, this method will throw an error. Information about the queried item is provided in a dictionary with the following keys. Note that not all information is provided or necessarily relevant for the kind of product you're querying.

KeyTypeDescription
NamestringThe name shown on the asset's page.
DescriptionstringThe description as shown on the asset's page; can be nil if blank.
PriceInRobuxnumberThe cost of purchasing the asset using Robux.
CreatedstringTimestamp of when the asset was created, for example 2022-01-02T10:30:45Z. Formatted using ISO 8601.
UpdatedstringTimestamp of when the asset was last updated by its creator, for example 2022-02-12T11:22:15Z. Formatted using ISO 8601.
ContentRatingTypeIdnumberIndicates whether the item is marked as 13+ in catalog.
MinimumMembershipLevelnumberThe minimum subscription level necessary to purchase the item.
IsPublicDomainbooleanDescribes whether the asset can be taken for free.

Creator Information

KeyTypeDescription
CreatortableDictionary table of information describing the creator of the asset (see following lines).
Creator.CreatorTypestringEither User or Group.
Creator.CreatorTargetIdnumberThe ID of the creator user or group.
Creator.HasVerifiedBadgebooleanWhether the creator has a verified badge.
Creator.NamestringThe name/username of the creator.
Creator.IdnumberUse Creator.CreatorTargetId instead.

Asset Information

KeyTypeDescription
AssetIdnumberIf Enum.InfoType was Asset, the ID of the given asset.
AssetTypeIdnumberThe type of asset. See Enum.AssetType for the asset type ID numbers.
IsForSalebooleanDescribes whether the asset is purchasable.
IsLimitedbooleanDescribes whether the asset is a Roblox Limited that is no longer (if ever) sold.
IsLimitedUniquebooleanDescribes whether the asset is a unique Roblox Limited ("Limited U") item that only has a fixed number sold.
IsNewbooleanDescribes whether the asset is marked as "new" in the catalog.
RemainingnumberThe remaining number of items a limited unique item may be sold.
SalesnumberThe number of items the asset has been sold.
SaleAvailabilityLocationstableThe item's ProductLocationRestriction or sale location setting.
CanBeSoldInThisGamebooleanDescribes whether the asset is purchasable in the current experience.

Developer Products and Passes

KeyTypeDescription
ProductIdnumberIf Enum.InfoType was Product, the product ID.
IconImageAssetIdnumberThe asset ID of the product/pass icon, or 0 if there isn't one.

Parameters

assetId: number

The asset ID of the specified product.

infoType: Enum.InfoType

An Enum.InfoType enum value specifying the type of information being retrieved.

Default Value: "Asset"

Returns

A dictionary containing information about the queried item, described in the previous tables.

Code Samples

Getting Product Info

local MarketplaceService = game:GetService("MarketplaceService")
local ASSET_ID = 125378389
local asset = MarketplaceService:GetProductInfo(ASSET_ID)
print(asset.Name .. " :: " .. asset.Description)

GetSubscriptionProductInfoAsync

yields

Note: Because it returns a localized price, you can only call this method from a Script with Enum.RunContext.Client.

Returns the product information of a subscription for the given subscriptionId.

KeyTypeDescription
NamestringThe name of the subscription product.
DescriptionstringThe description of the subscription product.
IconImageAssetIdnumberThe asset ID of the subscription product icon.
SubscriptionPeriodEnum.SubscriptionPeriodThe duration of the subscription (for example, Month, Year, etc.).
DisplayPricestringLocalized price with the appropriate currency symbol for display (for example, $4.99). For users in unsupported countries, DisplayPrice returns a string without specific price information.
DisplaySubscriptionPeriodnumberLocalized subscription period text for display (for example, /month). Can be used together with DisplayPrice.
SubscriptionProviderNamestringName of the subscription benefit provider (for example, the name of the associated experience).
IsForSalebooleanTrue if the subscription product is available for sale.
PriceTiernumberA number that can be used to compare the price of different subscription products. This is not the actual price of the subscription (for example, 499).

Parameters

subscriptionId: string

The ID of the subscription to check.


Returns

GetUserSubscriptionDetailsAsync

yields

Returns a dictionary table containing the details of the user's subscription for the given subscriptionId. The table contains the following keys:

KeyTypeDescription
SubscriptionStateEnum.SubscriptionStateCurrent state of this particular subscription.
NextRenewTimeDateTime Renewal time for this current subscription. May be in the past if the subscription is in SubscribedRenewalPaymentPending state. This field is will be nil if the subscription will not renew, is Expired, or the user never subscribed.
ExpireTimeDateTime When this subscription expires. This field will be nil if the subscription is not cancelled or the user never subscribed.
ExpirationDetailstable Table containing the details of the subscription expiration. This field will be nil if the subscription is not in the Expired state. If populated, the table contains a ExpirationReason key of type Enum.SubscriptionExpirationReason describing why the subscription is expired.

Note that this method can only be called from a Script with RunContext of Server. If you only need to determine the IsSubscribed status of a user, it's recommended to use GetUserSubscriptionStatusAsync() as it is faster and more efficient for that particular purpose.

Parameters

user: Player

The Player object whose subscription details you want to check.

subscriptionId: string

The ID of the subscription to check.


Returns

GetUserSubscriptionPaymentHistoryAsync

yields

Note: You can only call this method from a Script with Enum.RunContext.Server.

Returns an Array that contains up to one year of the user's subscription payment history for the given subscriptionId, sorted from the most recent status to the least recent.

Each entry in the payment history Array contains the following keys:

KeyTypeDescription
CycleStartTimeDateTimeDateTime at the start of this particular subscription period.
CycleEndTimeDateTimeDateTime at the end of this particular subscription period.
PaymentStatusEnum.SubscriptionPaymentStatusEnum.SubscriptionPaymentStatus.Paid if the user paid for this particular subscription period. Enum.SubscriptionPaymentStatus.Refunded if the user refunded this particular subscription period.

Payment History Length

Only creators affiliated with the subscription product can access up to one year worth of the user's subscription payment history. Non-associated creators can only get the user's current subscription payment status or an empty Array if the user has no active subscription.

Grace Period

Subscription renewal payments can have some processing time. Payment history doesn't return a table for this period. However, in order to preserve a user's subscription experience during the processing period, GetUserSubscriptionStatusAsync() returns IsSubscribed: true for the given user. Don't grant durable items or currency type subscription benefits to the user until after payment has been confirmed for the current cycle.

For example, on August 31, 2023, User A's Subscription B is up for renewal. On September 1, 2023, the payment has yet to be processed. If you call GetUserSubscriptionPaymentHistoryAsync() on September 1, 2023 on User A for Subscription B, the first entry of the return value is:

KeyValue
CycleStartTime...
CycleEndTimeAugust 31, 2023
PaymentStatusEnum.SubscriptionPaymentStatus.Paid

Note that since the user is within the grace period, the cycle they have yet to pay for (September 1, 2023) does not appear in the return value at all. This field only populates after the payment has been received and processed.

At the same time, GetUserSubscriptionStatusAsync returns the following result until the renewal payment process fails or the user cancels:

KeyReturn
IsSubscribedTrue
IsRenewingTrue

Parameters

user: Player
subscriptionId: string

Returns

Code Samples

MarketplaceService:GetUserSubscriptionPaymentHistoryAsync

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local SUBSCRIPTION_ID = "EXP-0"
local function checkSubscriptionHistory(player: Player)
local subscriptionHistory = {}
local success, err = pcall(function()
subscriptionHistory = MarketplaceService:GetUserSubscriptionPaymentHistoryAsync(player, SUBSCRIPTION_ID)
end)
if not success then
warn(`Error while checking subscription history: {err}`)
return
end
if next(subscriptionHistory) then
-- User has some subscription history within the past 12 months.
-- Print the details of each payment entry from the subscription history.
print(`Player {player.Name} has subscribed to {SUBSCRIPTION_ID} before:`)
for entryNum, paymentEntry in subscriptionHistory do
local paymentStatus = tostring(paymentEntry.PaymentStatus)
local cycleStartTime = paymentEntry.CycleStartTime:FormatLocalTime("LLL", "en-us")
local cycleEndTime = paymentEntry.CycleEndTime:FormatLocalTime("LLL", "en-us")
print(`{entryNum}: {paymentStatus} ({cycleStartTime} - {cycleEndTime})`)
end
else
print(`Player {player.Name} has never subscribed to {SUBSCRIPTION_ID} before.`)
end
end
-- Call checkSubscriptionHistory for any players already in the game
for _, player in ipairs(Players:GetPlayers()) do
checkSubscriptionHistory(player)
end
-- Call checkSubscriptionHistory for all future players
Players.PlayerAdded:Connect(checkSubscriptionHistory)

GetUserSubscriptionStatusAsync

yields

Returns a table that contains the subscription status of the user for the given subscriptionId. The table contains the following keys:

KeyTypeDescription
IsSubscribedbooleanTrue if the user's subscription is active.
IsRenewingbooleanTrue if the user is set to renew this subscription after the current subscription period ends.

Note that IsSubscribed will be true only when a user has purchased the subscription and the payment has been successfully processed. If the payment for a user's initial subscription purchase is still processing or has failed, IsSubscribed returns false. To understand when a user's subscription status has changed, see the Players.UserSubscriptionStatusChanged event.

Parameters

user: Player

The Player object whose subscription status you want to check.

subscriptionId: string

The ID of the subscription to check for.


Returns

Code Samples

Check User Subscription Status

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local subscriptionID = "EXP-00000"
local function checkSubStatus(player)
local subStatus = {}
local success, message = pcall(function()
-- returns IsRenewing and IsSubscribed
subStatus = MarketplaceService:GetUserSubscriptionStatusAsync(player, subscriptionID)
end)
if not success then
warn("Error while checking if player has subscription: " .. tostring(message))
return
end
if subStatus["IsSubscribed"] then
print(player.Name .. " is subscribed with " .. subscriptionID)
-- Give player permissions associated with the subscription
end
end
Players.PlayerAdded:Connect(checkSubStatus)

PlayerOwnsAsset

yields

Returns whether the inventory of the given player contains an asset, given its ID. In the case that a query fails, this method will throw an error, so it's recommended to wrap calls to this method in pcall().

  • This method should not be used for passes since they use a separate ID system. Legacy passes that still depend on an asset ID should use UserOwnsGamePassAsync() instead of this method.
  • This method cannot be used to check for developer products since they can be purchased multiple times but not owned themselves. Instead, use a data store to save when a player has bought a developer product.

Parameters

player: Instance

A Player object whose inventory shall be tested for the ownership of the given asset.

assetId: number

The asset ID for which the given player's inventory shall be tested.


Returns

Indicates whether the given player's inventory contains the given asset.

Code Samples

Check for Item Ownership

local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
-- The item we're checking for: https://www.roblox.com/catalog/30331986/Midnight-Shades
local ASSET_ID = 30331986
local ASSET_NAME = "Midnight Shades"
local function onPlayerAdded(player)
local success, doesPlayerOwnAsset = pcall(MarketplaceService.PlayerOwnsAsset, MarketplaceService, player, ASSET_ID)
if not success then
local errorMessage = doesPlayerOwnAsset
warn(`Error checking if {player.Name} owns {ASSET_NAME}: {errorMessage}`)
return
end
if doesPlayerOwnAsset then
print(`{player.Name} owns {ASSET_NAME}`)
else
print(`{player.Name} doesn't own {ASSET_NAME}`)
end
end
Players.PlayerAdded:Connect(onPlayerAdded)

PlayerOwnsBundle

yields

Returns whether the inventory of given Player contains a bundle, given its ID. In the case that a query fails, this method will throw an error, so it's recommended to wrap calls to this method in pcall().

Parameters

player: Player

A Player object whose inventory shall be tested for the ownership of the given bundle.

bundleId: number

The bundle ID for which the given player's inventory shall be tested.


Returns

Indicates whether the given player's inventory contains the given bundle.

Code Samples

Check for Bundle Ownership

local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
-- The bundle we're checking for: https://www.roblox.com/bundles/589/Junkbot
local BUNDLE_ID = 589
local BUNDLE_NAME = "Junkbot"
Players.PlayerAdded:Connect(function (player)
local success, doesPlayerOwnBundle = pcall(function()
return MarketplaceService:PlayerOwnsBundle(player, BUNDLE_ID)
end)
if success == false then
print("PlayerOwnsBundle call failed: ", doesPlayerOwnBundle)
return
end
if doesPlayerOwnBundle then
print(player.Name .. " owns " .. BUNDLE_NAME)
else
print(player.Name .. " doesn't own " .. BUNDLE_NAME)
end
end)

UserOwnsGamePassAsync

yields

Returns true if the player with the given UserId owns the pass with the given gamePassId, not to be confused with an asset ID.

Caching Behavior

Results of this function are remembered so that repeated calls will return quicker. This function will always return true if the player owns the pass upon first entering a server after having purchased the pass. If the pass is purchased in-experience through PromptGamePassPurchase(), this function may return false due to the caching behavior. Conversely, if the player deletes the pass from their inventory, this function may return true despite the player not owning the pass.

Parameters

userId: number

The UserId of the Player whose inventory shall be queried.

gamePassId: number

Pass ID to check, not to be confused with an asset ID.


Returns

Events

PromptBundlePurchaseFinished

Parameters

player: Instance
bundleId: number
wasPurchased: bool

PromptGamePassPurchaseFinished

This event fires when a purchase dialog for a pass is closed. It fires as the dialog closes, when the player presses "Cancel" at the prompt or "OK" at the success/error message.

To avoid duplicate item grants, store the purchase in a data store. If multiple attempts are made, check if the item has been already granted. For repeatable purchases use developer products.

See Also

Parameters

player: Instance

The Player object for whom the prompt was shown.

gamePassId: number

The ID number of the pass shown in the prompt, not to be confused with an asset ID.

wasPurchased: bool

Whether the item was successfully purchased (false for errors and cancellations).


Code Samples

Handling Gamepass Purchase Finished

local MarketplaceService = game:GetService("MarketplaceService")
local function gamepassPurchaseFinished(...)
-- Print all the details of the prompt, for example:
-- PromptGamePassPurchaseFinished PlayerName 123456 false
print("PromptGamePassPurchaseFinished", ...)
end
MarketplaceService.PromptGamePassPurchaseFinished:Connect(gamepassPurchaseFinished)

PromptPremiumPurchaseFinished

This event fires when the Roblox Premium purchase modal closes, specifically when the player presses "Cancel" at the prompt, "OK" at the error message, or after the payment UI closes.

See Also


PromptProductPurchaseFinished

Fires when a purchase prompt closes for a developer product, right as the dialog closes when the player presses "Cancel" at the prompt or "OK" at the success/error message. You can use this event to detect when a purchase prompt is closed, but it should not be used to process purchases; instead use MarketplaceService.ProcessReceipt.

Unlike the similarly-named events above, this event fires with a Player.UserId and not a reference to the Player object.

Parameters

userId: number

The UserId of the player for whom the prompt was shown.

productId: number

The ID number of the developer product shown in the prompt (not to be confused with an asset ID).

isPurchased: bool

Whether the item was successfully purchased (false for errors and cancellations).


PromptPurchaseFinished

Fires when a purchase prompt for an affiliate gear sale or other asset is closed. This fires right as the dialog closes, when the player presses "Cancel" at the prompt or "OK" at the success/error message. Does not fire for developer product or pass prompts.

See Also

Parameters

player: Instance

The Player object for whom the prompt was shown.

assetId: number

The asset ID of the item shown in the prompt.

isPurchased: bool

Whether the item was successfully purchased (false for errors and cancellations).


Code Samples

Handling PromptPurchaseFinished Event

local MarketplaceService = game:GetService("MarketplaceService")
local function onPromptPurchaseFinished(player, assetId, isPurchased)
if isPurchased then
print(player.Name, "bought an item with AssetID:", assetId)
else
print(player.Name, "didn't buy an item with AssetID:", assetId)
end
end
MarketplaceService.PromptPurchaseFinished:Connect(onPromptPurchaseFinished)

PromptSubscriptionPurchaseFinished

This event fires when the subscription purchase modal closes, specifically when the users presses Cancel, OK at the error message, or after the payment UI closes.

See Also

Parameters

user: Player

User whose subscription status has changed.

subscriptionId: string

The ID of the subscription with a status change.

didTryPurchasing: bool

Whether the user attempted to purchase the subscription.


Parameters

receiptInfo: Dictionary

Returns

Code Samples

ProcessReceipt Callback

local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
-- Data store for tracking purchases that were successfully processed
local purchaseHistoryStore = DataStoreService:GetDataStore("PurchaseHistory")
-- Table setup containing product IDs and functions for handling purchases
local productFunctions = {}
-- ProductId 123123 for a full heal
productFunctions[123123] = function(_receipt, player)
-- Logic/code for player buying a full heal (may vary)
if player.Character and player.Character:FindFirstChild("Humanoid") then
-- Heal the player to full health
player.Character.Humanoid.Health = player.Character.Humanoid.MaxHealth
-- Indicate a successful purchase
return true
end
end
-- ProductId 456456 for 100 gold
productFunctions[456456] = function(_receipt, player)
-- Logic/code for player buying 100 gold (may vary)
local stats = player:FindFirstChild("leaderstats")
local gold = stats and stats:FindFirstChild("Gold")
if gold then
gold.Value = gold.Value + 100
-- Indicate a successful purchase
return true
end
end
-- The core 'ProcessReceipt' callback function
local function processReceipt(receiptInfo)
-- Determine if the product was already granted by checking the data store
local playerProductKey = receiptInfo.PlayerId .. "_" .. receiptInfo.PurchaseId
local purchased = false
local success, result, errorMessage
success, errorMessage = pcall(function()
purchased = purchaseHistoryStore:GetAsync(playerProductKey)
end)
-- If purchase was recorded, the product was already granted
if success and purchased then
return Enum.ProductPurchaseDecision.PurchaseGranted
elseif not success then
error("Data store error:" .. errorMessage)
end
-- Determine if the product was already granted by checking the data store
local playerProductKey = receiptInfo.PlayerId .. "_" .. receiptInfo.PurchaseId
local success, isPurchaseRecorded = pcall(function()
return purchaseHistoryStore:UpdateAsync(playerProductKey, function(alreadyPurchased)
if alreadyPurchased then
return true
end
-- Find the player who made the purchase in the server
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
-- The player probably left the game
-- If they come back, the callback will be called again
return nil
end
local handler = productFunctions[receiptInfo.ProductId]
local success, result = pcall(handler, receiptInfo, player)
-- If granting the product failed, do NOT record the purchase in datastores.
if not success or not result then
error("Failed to process a product purchase for ProductId: " .. tostring(receiptInfo.ProductId) .. " Player: " .. tostring(player) .. " Error: " .. tostring(result))
return nil
end
-- Record the transcation in purchaseHistoryStore.
return true
end)
end)
if not success then
error("Failed to process receipt due to data store error.")
return Enum.ProductPurchaseDecision.NotProcessedYet
elseif isPurchaseRecorded == nil then
-- Didn't update the value in data store.
return Enum.ProductPurchaseDecision.NotProcessedYet
else
-- IMPORTANT: Tell Roblox that the game successfully handled the purchase
return Enum.ProductPurchaseDecision.PurchaseGranted
end
end
-- Set the callback; this can only be done once by one script on the server!
MarketplaceService.ProcessReceipt = processReceipt