What's the purpose of recording a successful ProcessReceipt?

On this page, the code includes a check to see if a purchase was already granted, as seen here:

local playerProductKey = receiptInfo.PlayerId .. ":" .. receiptInfo.PurchaseId
if PurchaseHistory:GetAsync(playerProductKey) then
    return Enum.ProductPurchaseDecision.PurchaseGranted --We already granted it.

Of course later, they save the purchase like this:

-- record the transaction in a Data Store
suc,err = pcall(function()
    PurchaseHistory:SetAsync(playerProductKey, true)

Why do this? Shouldn’t ProcessReceipt not run for an already successful purchase? It doesn’t make sense to have to use up some of our data store budget for something that shouldn’t be running again anyway, so what’s the purpose of doing this?

As far as I know, saving a player’s purchase to DataStore is purely for records reasons, since there’s no easy way to know if they’ve purchased a developer product otherwise. That way, if they contact you with an issue, you can check the DataStore and make any necessary changes.


It is good practice, but you don’t necessarily have to.

Side note, the datastore budget isn’t really a limiting factor. Games already seem to get way more allotted datastore actions than I could come up with a usecase for.

The implementation of it here isn’t useful for that, as we would need the purchase ID of the purchase which as far as I know we can’t obtain anywhere, which means this isn’t for that.

Hmm, that is odd. It would make more sense to just add the purchase to an existing table of previous purchases of that product by that particular player, imo.

What you should typically do is store the receipt IDs of the players’ purchases together with the player data while you’re also performing a save operation on it. So if you have a game pass that gives 100 coins, you set both 100 coins into the players’ save data as well as put the receipt ID in their data in the same call. You should also check at each PurchaseReceipt whether that receipt ID was already awarded.

If you don’t do this, you could run into issues when i.e. the server hasn’t polled back to the main service that the purchase was successful, but the player teleports to a different place/instance in your game, and then it tries to call the same receipt ID there again. There are probably other edge cases where there is a small (but not 0%) possibility of the receipt ID to be fired multiple times. And of course, if ProcessReceipt fires multiple times because of a bug in the engine (this has happened before), it would be nice if your code could handle this.

Basically, if you see that the receipt was already processed before, you’d want to return PurchaseGranted and jump out of the ProcessReceipt call straight away. This is the cleanest way to work with receipt IDs and ProcessReceipt.