Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local marketplaceService = game:GetService("MarketplaceService")
- local Players = game:GetService("Players")
- local replicatedStorage = game:GetService("ReplicatedStorage")
- local eventsFolder = replicatedStorage:WaitForChild("EventsFolder")
- local purchaseEvent = eventsFolder:WaitForChild("PurchaseEvent")
- local savePlayerProfile = eventsFolder:WaitForChild("SavePlayerProfile") -- TODO make this a BindableFunction. Make sure that in your Datastore script, you save when this is called. It will be given the 'player' whose data to save.
- local products = {} --keys: name and IDs; value has .Name, .Offers, .ApplyOffer
- local idToProduct = {}
- local idToAmount = {}
- function CreateProduct(name, offers, applyOffer)
- -- offers: dictionary between amount offered (the key) and cost to user (the value)
- -- applyOffer: function(player, amount)
- if products[name] then error("Already have a product with name " .. name .."!") end -- Make sure we don't make a mistake (ex having 2 "Gold" products, which would break this algorithm)
- -- We need to be able to look up each offer by Amount, so turn 'offers' into a dictionary
- local offerDict = {}
- for i = 1, #offers do
- offerDict[offers[i].Amount] = offers[i]
- end
- local product = {Name = name, Offers = offerDict, ApplyOffer = applyOffer}
- products[name] = product
- for i = 1, #offers do
- idToProduct[offers[i].ID] = product
- idToAmount[offers[i].ID] = offers[i].Amount
- end
- end
- CreateProduct("Gold", {
- {ID = 100122331, Amount = 50, Price = 50},
- {ID = 111111111, Amount = 100, Price = 100},
- --etc
- }, _G.awardGold)
- -- ALTERNATIVE to above (starting at CreateProduct):
- function CreateProduct(name, offers, applyOffer)
- -- offers: dictionary between amount offered (the key) and cost to user (the value)
- -- applyOffer: function(player, amount)
- if products[name] then error("Already have a product with name " .. name .."!") end -- Make sure we don't make a mistake (ex having 2 "Gold" products, which would break this algorithm)
- local product = {Name = name, Offers = offers, ApplyOffer = applyOffer}
- products[name] = product
- for i = amount, offer in pairs(offers) do
- idToProduct[offer.ID] = product
- idToAmount[offer.ID] = amount
- end
- end
- CreateProduct("Gold", {
- [50] = {ID = 100122331, Price = 50},
- [100] = {ID = 111111111, Price = 100},
- --etc
- }, _G.awardGold)
- -- END OF ALTERNATIVE. The alternative is simpler, but the "[50]" is not as descriptive as Amount=50.
- purchaseEvent.OnServerEvent:connect(function(player, name, amount)
- local product = products[name]
- if not product then error(string.format("Player %s attempted to purchase amount %s of non-existent %s", player.Name, amount, name)) end
- local offer = product.Offers[amount]
- if not offer then error(string.format("Player %s attempted to purchase non-existent amount %s of %s", player.Name, amount, name)) end
- marketplaceService:PromptPurchase(player, offer.ID, false)
- end)
- --System to record purchases temporarily (in case datastores aren't working or have slown down)
- local recentPurchases = {} -- Dictionary<player, Dictionary<PurchaseId, value>>. values:
- local IN_PROGRESS = 0 -- Player purchased it and was given reward, but it wasn't saved yet
- local COMPLETE = 1 -- Purchase complete and fully saved
- Players.PlayerAdded:Connect(function(player)
- recentPurchases[player] = {}
- end)
- Players.PlayerRemoving:Connect(function(player)
- recentPurchases[player] = nil
- end)
- function PlayerMadePurchase(player, purchaseID) -- True if the player at least started a purchase
- return recentPurchases[player][purchaseID]
- end
- function PlayerPurchaseComplete(player, purchaseID) -- True if the player finished the purchase
- return recentPurchases[player][purchaseID] == COMPLETE
- end
- function RecordPlayerPurchaseInProgress(player, purchaseID)
- recentPurchases[player][purchaseID] = IN_PROGRESS
- end
- function RecordPlayerPurchaseComplete(player, purchaseID)
- recentPurchases[player][purchaseID] = COMPLETE
- delay(300, function() -- Release memory after a while (but give enough time for Roblox to fire ProcessReceipt incorrectly, as suggested might happen by wiki)
- if recentPurchases[player] then -- if player is still around
- recentPurchases[player][purchaseID] = nil -- release memory
- end
- end)
- end
- --ProcessReceipt
- function marketplaceService.ProcessReceipt(receiptInfo)
- -- find the player based on the PlayerId in receiptInfo
- local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
- -- player left? don't process it:
- if not player then return Enum.ProductPurchaseDecision.NotProcessedYet end
- local purchaseID = receiptInfo.PurchaseId
- if PlayerMadePurchase(player, purchaseID) then
- if PlayerPurchaseComplete(player, purchaseID) then
- return Enum.ProductPurchaseDecision.PurchaseGranted --We already granted it.
- end
- --Attempt to save profile again
- return AttemptToFinishPurchase(player, purchaseID)
- end
- -- Get information required to give the player what they purchased
- local product = idToProduct[receiptInfo.ProductId]
- local amount = idToAmount[receiptInfo.ProductId]
- -- call ApplyOffer safely with pcall to catch any error
- local suc, err = pcall(product.ApplyOffer, player, amount)
- if not suc then -- log it to the output
- warn("An error occured while processing a product purchase")
- print("\tProduct name:", product.Name)
- print("\tProduct amount:", amount)
- print("\tProductId:",receiptInfo.ProductId)
- print("\tPlayer:",player)
- print("\tError message:",err)
- return Enum.ProductPurchaseDecision.NotProcessedYet
- end
- -- if the function didn't error, 'err' will be whatever the function returned
- -- if our handler didn't return anything (or it returned false/nil), it means
- -- that the purchase failed for some reason, so we have to cancel it
- if not err then
- return Enum.ProductPurchaseDecision.NotProcessedYet
- end
- RecordPlayerPurchaseInProgress(player, receiptInfo.PurchaseId)
- return AttemptToFinishPurchase(player, purchaseID)
- end
- function AttemptToFinishPurchase()
- -- Ask for the player's profile to be saved
- if not savePlayerProfile:Invoke(player) then
- -- Save failed, try again later
- return Enum.ProductPurchaseDecision.NotProcessedYet
- end
- RecordPlayerPurchaseComplete(player, receiptInfo.PurchaseId)
- -- tell ROBLOX that we have successfully handled the transaction (required)
- return Enum.ProductPurchaseDecision.PurchaseGranted
- end
Advertisement
Add Comment
Please, Sign In to add comment