How to use ProcessReceipt?

--[[ Buy items ]]--
function itemBuy(player, itemType, item, currency, cost)
	if player and currency and cost then
		local myData = playerData[player.UserId]
		if myData then
			if currency == 'Cash' then
				--[[ Cash purchase ]]--
				if myData.cash >= cost then
					updateCash:Fire(player, -cost)
					purchase(player, itemType, item) -- For character items
					return true
				else
					-- Not enough cash to buy
					return false
				end
			else
				--[[ Robux purchase ]]--
				marketplaceService:PromptProductPurchase(player, cost) -- Cost == ProductId

				local purchaseHistory = dataStoreService:GetDataStore('PurchaseHistoryTest')
 
				function marketplaceService.ProcessReceipt(receiptInfo) 
					local playerProductKey = receiptInfo.PlayerId .. ':' .. receiptInfo.PurchaseId
					if purchaseHistory:GetAsync(playerProductKey) then
						return Enum.ProductPurchaseDecision.PurchaseGranted
					end
					
					if not game:GetService('Players'):GetPlayerByUserId(receiptInfo.PlayerId) then
						return Enum.ProductPurchaseDecision.NotProcessedYet
					end
				 
					if receiptInfo.ProductId == cost then
						purchase(player, itemType, item)
						return true
					end
					purchaseHistory:SetAsync(playerProductKey, true)	
					return Enum.ProductPurchaseDecision.PurchaseGranted	
				end
			end
		end
	end
end
 
buyItem.OnServerInvoke = itemBuy

As you can see I’m trying to use a RemoteFunction, that fires whenever a player clicks buy, and if they purchase the item it returns true. Problem with Robux purchases is now you have to use ‘ProcessReceipt’ which seems like a lot more work than what was needed before (PromptProductPurchaseFinished) and with all this data storing which makes no sense whatsoever. All I want is to check if the player has purchased a developer product, return true, so the LocalScript can continue with what it wants to do. Problem is, according to the wiki, the ProcessReceipt script needs like half a dozen returns, which messes with my main return.

In short I have no clue what a single line of the ProcessReceipt does. I just want something to fire when a player buys a product. No saving any of it to datastores or whatever.

EDIT
And can anyone explain why this is only for dev products? It’s very easy to check if someone has purchased a gamepass or asset with just a line. Why do dev products need to be so long and over complicated all of a sudden?

1 Like

Dev products are extremely flexible and as such, implementation is completely up to the developer. They are only purchasable in-game so you need to handle the purchase in-game. If you fail to do so then the ProcessReceipt function will be called every time the player joins the game in future.

You need to define the ProcessReceipt function in the main scope of your script, before you prompt purchase. Your ProcessReceipt function should be set up in such a way that it can handle and verify old purchases from previous sessions marked as “NotProcessedYet” as well as new ones in the current session.

If you are trying to get a return value of true or false and you want your script to “wait” for that response, you’ll need something along the following lines:

local Players = game:GetService( 'Players' )
local RunService = game:GetService( 'RunService' )
local PurchaseHistory = game:GetService( 'DataStoreService' ):GetDataStore( 'PurchaseHistory' )
local pendingPurchases = {}

function marketplaceService.ProcessReceipt( receiptInfo )
    -- For data storage purposes, and handling purchases that couldn't be caught first time round
    local playerProductKey = receiptInfo.PlayerId .. ':' .. receiptInfo.PurchaseId
    if PurchaseHistory:GetAsync( playerProductKey ) then
        return Enum.ProductPurchaseDecision.PurchaseGranted
    end

    local player = Players:GetPlayerByUserId( receiptInfo.PlayerId )
    if not player then
        return Enum.ProductPurchaseDecision.NotProcessedYet
    end

    PurchaseHistory:SetAsync( playerProductKey, true )	
    return Enum.ProductPurchaseDecision.PurchaseGranted
end

function triggerPurchase( player, cost )
    -- Triggering a purchase
    local userId = player.UserId
    if not pendingPurchases[ userId ] then
        pendingPurchases[ userId ] = {}
    end
    pendingPurchases[ userId ][ cost ] = nil
    marketplaceService:PromptProductPurchase( player, cost )
    repeat
        RunService.Stepped:Wait()
    until typeof( pendingPurchases[ userId ][ cost ] ) == 'boolean' ) or not Players:GetPlayerByUserId( userId )
    return pendingPurchases[ userId ] and pendingPurchases[ userId ][ cost ]
end

marketplaceService.PromptProductPurchaseFinished:Connect( function ( userId, productId, isPurchased )
    -- For detecting purchase status within the current session
    if pendingPurchases[ userId ] then
        pendingPurchases[ userId ][ productId ] = isPurchased
    end
end )

Players.PlayerRemoving:Connect( function ( player )
    if pendingPurchases[ player.UserId ] then
        pendingPurchases[ player.UserId ] = nil
    end
end )

-- Trigger the purchase:
local purchaseCompleted = triggerPurchase( player, cost )
    -- returns true, false or nil based on success, failure or player left

So after triggering a purchase it waits until a response is obtained or the player has left.

Please note that ProcessReceipt is not something new. You need to focus on the difference between dev products and gamepasses. From looking at your code you’d be better off with gamepasses as it seems like you only want a one-time sale. Dev products are for infinite sales of something that you want to record in-game.

They’ve always been this “long and over complicated” and if you haven’t been doing this in the past then it’s possible that you’ve been receiving purchases without handling them correctly or not receiving the robux you should by not implementing it correctly.

3 Likes

Not sure if you’ve got a solution yet, but you don’t need to use a RemoteEvent to detect if it’s been purchased.
Check this out if you haven’t already.

Inside the code provided in the wiki, you can fire to the client to update the cash, but there is no reason to Fire to the server.

Not sure if I missed something but hope this helps!

(post deleted by author)

I believe that it’d probably be easier for you to just use DataStore2’s method (backups) instead of storing puchases, although how well it’ll work will depend on how well you make it work.