For critical purchases (like currency or permanent perks), you can use a more robust method that yields the ProcessReceipt callback until the purchase is confirmed to be saved in the DataStore. This prevents loss of items in the rare event of a server crash occurring between the data modification and the automatic save.
How it works:
- Cache PurchaseIds: Store a list of
PurchaseIds in your profile.Data (e.g., profile.Data.PurchaseIdCache). - Yield the Callback: Use a helper function (like
PurchaseIdCheckAsync) that yields inside ProcessReceipt. - Verify via
LastSavedData: The helper function periodically calls profile:Save() and checks profile.LastSavedData to see if the PurchaseId has been successfully written to the underlying DataStore. - Grant and Confirm: Only return
Enum.ProductPurchaseDecision.PurchaseGranted once the PurchaseId is found in profile.LastSavedData.
Note: This requires maintaining a PurchaseIdCache within your profile data schema.
-- Implementation snippet for caching PurchaseIds
function PurchaseIdCheckAsync(profile, purchase_id, grant_product): Enum.ProductPurchaseDecision
if profile:IsActive() == true then
local purchase_id_cache = profile.Data.PurchaseIdCache
if purchase_id_cache == nil then
purchase_id_cache = {}
profile.Data.PurchaseIdCache = purchase_id_cache
end
-- Granting product if not already received in this session
if table.find(purchase_id_cache, purchase_id) == nil then
local success, result = pcall(grant_product)
if success ~= true then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
table.insert(purchase_id_cache, purchase_id)
end
-- Wait until the purchase is confirmed to be saved to the DataStore
local function is_purchase_saved()
local saved_cache = profile.LastSavedData.PurchaseIdCache
return if saved_cache ~= nil then table.find(saved_cache, purchase_id) ~= nil else false
end
if is_purchase_saved() == true then
return Enum.ProductPurchaseDecision.PurchaseGranted
end
while profile:IsActive() == true do
local last_saved_data = profile.LastSavedData
profile:Save()
if profile.LastSavedData == last_saved_data then
profile.OnAfterSave:Wait()
end
if is_purchase_saved() == true then
return Enum.ProductPurchaseDecision.PurchaseGranted
end
if profile:IsActive() == true then
task.wait(10)
end
end
end
return Enum.ProductPurchaseDecision.NotProcessedYet
end