Use when adding or changing developer products, game passes or anything in MarketplaceService. Makes purchase handling reliable so players always get what they paid for exactly once.
Use the robloxvibecoders MCP: call get_resource with id "skill-roblox-purchase-safety" and install it as a Claude Code skill in this project (.claude/skills/roblox-purchase-safety/SKILL.md).
Skills in ~/.claude/skills work in every project. Put them in your project's .claude/skills folder to share them with your team.
What your AI reads
SKILL.md
---
name: roblox-purchase-safety
description: Use when adding or changing developer products, game passes or anything in MarketplaceService. Makes purchase handling reliable so players always get what they paid for exactly once.
---
# Safe Robux purchases
Lost or doubled purchases cost real money and trust. Follow these rules for every product.
## Developer products (ProcessReceipt)
- Set `MarketplaceService.ProcessReceipt` **once**, in one server script. If two scripts set it, only one of them
wins.
- Inside the callback:
1. Find the player from `receiptInfo.PlayerId`. If they've left, return
`Enum.ProductPurchaseDecision.NotProcessedYet`.
2. If `receiptInfo.PurchaseId` was already granted (it's stored in the player's saved data), return
`PurchaseGranted` without granting again.
3. Look up the reward by `receiptInfo.ProductId` in a server-side table.
4. Grant it, record the `PurchaseId`, and **save**.
5. Return `PurchaseGranted` only after the save succeeds. If anything fails, return `NotProcessedYet` so Roblox
retries later.
- Never grant from the client, and never grant from `PromptProductPurchaseFinished`: it isn't proof of payment.
## Game passes
- Check ownership on the server with `MarketplaceService:UserOwnsGamePassAsync(userId, passId)`, inside `pcall`.
Cache the result per session.
- Grant the perk in `PromptGamePassPurchaseFinished` on the server when `wasPurchased` is true, and on join when
they already own it.
## Prompts
- The client may only *ask* for a prompt. Prices, IDs and rewards live in server tables.
- Call `PromptProductPurchase` or `PromptGamePassPurchase` from a server script or a validated client request.
## Before you finish
Explain what happens if the player leaves mid-purchase, if the save fails, and if Roblox calls `ProcessReceipt`
twice with the same `PurchaseId`.