Кнопка доната только открывает системное окно покупки. Награду выдаёт только сервер через ProcessReceipt. Так клиент не может подделать успешную оплату, а необработанная квитанция может быть повторена.
Developer Product подходит для повторяемой покупки: монет, пополнения здоровья или добровольной поддержки. Для постоянного предмета, который покупают один раз, нужен Pass. В этой статье наградой будут 100 монет, чтобы весь цикл можно было проверить по leaderstats.
LocalScript вызывает PromptProductPurchase с нужным productId.
Roblox передаёт квитанцию в серверный ProcessReceipt.
Сервер находит игрока, добавляет монеты и подтверждает обработку.
Чем Developer Product отличается от Pass
Сначала опубликуйте experience, затем в Creator Hub откройте Monetization и Developer Products. Создайте продукт и скопируйте его ID, не ID места и не ID картинки. Кнопка лежит в ScreenGui, LocalScript рядом с ней, а обработчик квитанций в ServerScriptService.
| Покупка | Тип | Как часто |
|---|---|---|
| 100 монет | Developer Product | Многократно |
| Постоянный VIP | Pass | Один раз |
| Доступ на месяц | Subscription | По периоду |
| Добровольная поддержка | Developer Product | Многократно |
После этого шага у каждой части есть одна роль. Интерфейс собирает действие, логика меняет состояние, а вывод показывает признак успеха. Это проще отлаживать, чем один большой обработчик.

Клиент просит, сервер выдаёт. PurchaseGranted возвращается после выдачи
Пишем клиентскую кнопку и ProcessReceipt
Две половины нельзя склеивать в один LocalScript. Клиент знает только productId и открывает покупку. Сервер принимает receiptInfo от Roblox, проверяет ProductId и только после успешной выдачи возвращает PurchaseGranted.
-- LocalScript в TextButton
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local PRODUCT_ID = 123456789 -- замените на свой ID
script.Parent.Activated:Connect(function()
MarketplaceService:PromptProductPurchase(Players.LocalPlayer, PRODUCT_ID)
end)
-- Script в ServerScriptService
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local PRODUCT_ID = 123456789
local COINS_PER_PURCHASE = 100
MarketplaceService.ProcessReceipt = function(receiptInfo)
if receiptInfo.ProductId ~= PRODUCT_ID then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local leaderstats = player:FindFirstChild("leaderstats")
local coins = leaderstats and leaderstats:FindFirstChild("Coins")
if not coins then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
coins.Value += COINS_PER_PURCHASE
return Enum.ProductPurchaseDecision.PurchaseGranted
endКлиент не выдаёт себе монеты. Незнакомый ProductId не подтверждается. Отсутствующий игрок или Coins дают Roblox повторить квитанцию. Подтверждение возвращается только после изменения Coins.

Граница доверия в покупке. Сравнение показывает, какой вариант легче поддерживать и проверять.
Почему нельзя верить PromptProductPurchaseFinished
PromptProductPurchaseFinished сообщает о закрытии диалога, но не является надёжным доказательством оплаты. Официальная схема требует ProcessReceipt на сервере. Одна и та же квитанция может прийти повторно, поэтому в продакшене её связывают с PurchaseId и хранят факт выдачи атомарно.
| Сигнал | Можно доверять | Роль |
|---|---|---|
| Клик по кнопке | Нет | Только открывает диалог |
| PromptProductPurchaseFinished | Нет | Не выдаёт награду |
| ProcessReceipt | Да | Проверяет квитанцию на сервере |
| PurchaseGranted | Да, после выдачи | Закрывает обработку |
Сохраняйте в одном месте то, что может измениться. Если одно и то же значение скопировано в три функции, после первой правки они начнут расходиться. Один объект состояния и одна функция отрисовки снимают эту проблему.
Практика: тестируем весь путь покупки
Покупки в тестовом режиме могут стоить реальные Robux, поэтому используйте недорогой продукт и проверяйте актуальные условия Creator Hub. До теста добавьте leaderstats и Coins, иначе квитанция будет откладываться. Смотрите Output и значение Coins на сервере, а не рисуйте фальшивую галочку на клиенте.
- Опубликуйте experience и создайте Developer Product.
- Замените 123456789 в обоих частях кода на свой productId.
- Запустите игру и нажмите кнопку один раз.
- Отмените диалог и проверьте, что Coins не изменились.
- Проведите одну контролируемую покупку и сверьте прибавку.
- Перезайдите в игру и убедитесь, что отложенные квитанции не теряются.
Кнопка открывает системный диалог с верным продуктом. Отмена не меняет Coins. Успешная покупка даёт ровно 100 монет. Сервер возвращает NotProcessedYet, если не смог выдать награду.

До того, как кнопку увидят игроки. Пройдите все четыре сценария перед публикацией проекта.
Доводим кнопку доната до готового вида
Не прячьте цену и не подменяйте системное окно своим. Кнопка должна называть награду и давать понять, что покупка повторяется. Если это именно поддержка автора, награду можно сделать небольшой и не влияющей на баланс. На мобильном экране не ставьте кнопку поверх штатных элементов управления.
Клиенту нельзя доверять результат покупки.
Это событие не заменяет ProcessReceipt.
Квитанция закроется, а игрок может не получить покупку.
Обработчик должен быть один и маршрутизировать ProductId.
Что получится в итоге

В готовом интерфейсе игрок видит награду и кнопку, а после оплаты сервер начисляет монеты.
Когда нужен Developer Product?
Когда один и тот же товар можно покупать многократно.
Где выдавать награду?
Только на сервере в ProcessReceipt после проверки receiptInfo.
Что вернуть, если Coins ещё нет?
NotProcessedYet, чтобы Roblox повторил квитанцию позже.
Как понять, что проект готов?
Он повторяет целевой сценарий несколько раз подряд, понятно реагирует на ошибочный ввод и не теряет состояние при обычных действиях.
Можно ли копировать код целиком?
Можно как стартовую точку, но сначала замените значения из примера, затем запустите проект по шагам. Если вы не можете объяснить одну строку, её лучше разобрать до следующего шага.
Клиентские и серверные скрипты разбираем в курсе Lua. Кнопку можно встроить в магазин Roblox.
Для счёта монет пригодится таблица leaderstats, а для вечного результата нужно DataStore. В Кодике можно отдельно прогнать каждую функцию до переноса в Studio.