How to add custom donations to your game using DynamicDonations

Hello everyone! I recently released DynamicDonations, an open source software that enables you to dynamically create developer products for your games.

Use case

Have you ever added donations to your game? If you did, you probably added some UI buttons that prompt the player to purchase a developer product to make the donation. Although it works, you are limited to setting caps and minimums on donation amounts and this might push players away from donating because they don’t have enough robux.

With DynamicDonations you can programmatically create developer products of a player-defined price so that everyone can donate as much or as little as they want.

What you will learn

In this tutorial you will learn how to integrate DyamicDonations in your game for free.

Configuring the server code

DynamicDonations by default requires a SSL certificate to start and enable HTTPS on your API, although if you run it on Render without using a custom domain, your API will be protected by Render’s SSL certificate.

:warning: Roblox requires a HTTPS connection to allow interacting with external APIs through HttpService

If you do not provide a SSL certificate, the server will crash on startup.
If you do not have a SSL certificate, you can use a modified version of DynamicDonations that you can find on my GitHub profile here

  1. Navigate to the GitHub repository
  2. Click “Fork” in the top right corner
  3. From the dropdown menu choose the forked repository owner
  4. Click “Create fork”

:information_source: If you want to deploy DynamicDonations with a SSL certificate, you can find the documentation to do so in the README.md file

Setting up servers

There are two great tools you can use to host DynamicDonations for completely free. DynamicDonations relies on the Node.js runtime and the MySQL database.

You can get a free MySQL instance that runs 24/7 on Railway.
As for the server hosting provider you can use Render.

Setting up MySQL server and Render project

  1. Open the Railway dashboard
  2. Click immagine in the top right corner
  3. Click immagine
  4. Click on the deployed MySQL instance
    immagine
  5. Go to the variables tab
  6. Copy the MYSQL_URL variable using the copy button (immagine)

:warning: THE MYSQL_URL VARIABLE CONTAINS THE CREDENTIALS TO CONNECT AND INTERACT WITH YOUR DATABASE INSTANCE. KEEP IT PRIVATE!

  1. Open the Render dashboard
  2. Click immagine in the top right corner
  3. From the dropdown menu that appears, select “Web Service”
  4. Select “Build and deploy from a Git repository”
  5. Click Next
  6. If you did not sign up with GitHub, click “Connect GitHub”
    12.1) Select the account under which you forked the repository
    12.2) Under “Repository access”, select "Only select repositories
    12.3) From the dropdown menu, select the forked repository
    12.4) Click “Save”
    12.5) Go back to Render
  7. From the “Connect repository” list, click “Connect” on the forked repository
  8. As “Region” select one where you can connect using a VPN (VPN is required due to IP changes invalidating ROBLOSECURITY cookies)
  9. Leave “Root Directory” blank
  10. Select “Node” as Runtime
  11. Set “Build command” to npm i && npx prisma db push && npx prisma generate && tsc
  12. Set “Start Command” to node .
  13. Expand the “Advanced” tab
  14. Click “Add Environment Variable”
  15. Set “key” to API_KEY and for the value, either input a string you want to use as the API key that will be required for authentication to the DynamicDonations API or click the “Generate” button and Render will generate a random string for you
    21.1) Add DATABASE_URL and set the value to the URL you copied from the Railway dashboard
    21.2) Add HTTP_PORT and set the value to 80 which is the default port for the HTTP protocol
    21.3) Add NODE_ENV and set the value to production to tell the server to read the environment variables from Render rather than a .env file which is used for development only

:warning: Do not click “Create Web Service” yet

Setting up a Roblox bot account

It is good practice to use a bot account when you need to use its ROBLOSECURITY cookie.

  1. Connected to the region you chose on Render using a VPN

  2. Open Roblox in an incognito window

  3. Create a new account

  4. Once you are done creating the new account, open your browser’s dev tools.

    On Firefox

    1. Press F12 on your keyboard
    2. Go to the “Storage” tab
    3. Expand the “Cookies” section
    4. Select https://www.roblox.com
    5. Locate the .ROBLOSECURITY cookie in the list
    6. Double click on the “Value” field and copy it

    On Chromium based browsers

    1. Press F12 on your keyboard
    2. Go to the “Application” tab in the top area of the dev tools window
      2.1) If you don’t see an “Application” tab, click immagine
      2.2) From the dropdown menu, select “Application”
    3. Follow steps 3-6 of the “On Firefox” section above
    4. Close the incognito window
    5. You can now disconnect the VPN

    :warning: Make sure not to login to the bot account without being connected to the Render server region through a VPN. If you do, the .ROBLOSECURITY cookie of the account will be invalidated and your DynamicDonations deployment will stop working.

Continuing enumeration of Setting up MySQL server and Render project

  1. Add ROBLOSECURITY and set the value to the cookie’s value you copied through your browser’s dev tools.
  2. Click “Create Web Service”

Give Render a few minutes to download, build and start the DynamicDonations API server.
When the server is ready, Render will print Build successful 🎉 to the console.
Your DynamicDonations API is now available at the URL in the top left corner (<project-name>.onrender.com).

You have now successfully deployed the DynamicDonations API server.

Integrating in a Roblox game

Now that the DynamicDonations API is deployed, you need to let players create custom developer products.

I will not go over how to create a GUI and configure the server script as this tutorial assumes you already know how to.

Here’s a design you can follow to do this:

  1. Create a GUI with a TextBox and a TextButton. If you want to let players cover the Roblox tax on items purchases, you can add a checkbox to enable the user to do so
  2. When the button is clicked, use a RemoteEvent to send the amount of Robux typed by the user into the TextBox and the eventual coverTax bool value to the server
  3. In a server script, listen to the RemoteEvent and make the following call to the DynamicDonations API
local data = {
    data = {
        price = amountOfRobuxTheUserWantsToDonate
        coverTax = playerCoversTax -- or set it to false if you don't want to implement this feature
    }
}

local headers = {
    ["Content-Type"] = "application/json",
    ["x-api-key"] = "<The API key you set on Render">
}

local success, result = pcall(function()
    return HttpService:RequestAsync({
        Url = "<Your Render DynamicDonations API server URL>/api/v1/donations/dev-products",
        Method = "POST",
        Headers = headers,
        Body = HttpService:JSONEncode(data)
   })
end)

if success and result.Success then -- result.Success indicates if the DynamicDonations API returned a successful response
    local resBody = HttpService:JSONDecode(result.Body)
    local devProductId = resBody.data.devProductId

    -- Prompt the player to purchase the product using MarketplaceService
    -- NOTE: For the purchase to be successful you also need to implement a ProcessReceipt callback
end

:information_source: The bot account needs permission to edit the game in order to create a dev product for it. DynamicDonations automatically detects the Roblox game that sends the request and creates the dev product for it.

Note

Render pauses free projects for inactivity and restarts them when they are interacted with. This may cause slow API responses when the server is woken up. To address this issue you can setup a service that constantly interacts with the API that you can also use to monitor its status. I personally use Uptime by Betterstack.

If you have any questions feel free to ask!

8 Likes

Hi,
I scanned through this. If u are hosting on your pc , does that mean it needs to be up all the time ?

Do u have a .rbxl file ?

2 Likes

Yes if you want to host it on your PC you have to keep it running 24/7 and allow connections from external clients through your firewall which is not ideal.

No I don’t have a rbxl file but I can probably add one later today.

Using your Roblox cookie and having a bot account isn’t really a proper way to edit donations since they are security risks and you are not supposed to use a bot account for API purposes.

Will you ever considering using the Open Cloud and API Keys? Using the open cloud doesn’t require you to have a bot account and using the cookie. API keys allow you to give any the necessary permissions needed for that application along with the security of being IP limited, expiration dates, being able to be quickly revoked, and more

If your cookie is compromised then you are essentially giving away edit access to a random attacker which can’t be revoked or canceled quickly.

1 Like

Using your cookie in a protected environment is not a risk, it becomes one if that environment is compromised which would either require your hosting service account getting compromised or the service itself getting compromised.

It seems you don’t know that OpenCloud has no support for developer products as of right now, which is why this uses the account’s authentication cookie.

2 Likes

@NinjaFurfante07 Are you actively maintaining this? as i want to use it for a project

Yes, I actively maintain this project.

1 Like

Alright I have everything Setup However The ProcessReceipt is very Confusing to me, why should i do this and is it appropriate without?

Here is My Code So Far:

function AService:Test(user:Player,amount: number,coverTax: boolean)
	local data = {
		data = {
			price = amount,
			coverTax = coverTax,
		},
	}

	local headers = {
		"Content-Type" == "application/json",
		"api-key" == "API_KEY",
	}

	local success, result = pcall(function()
		return HttpService:RequestAsync({
			Url = APIserver .. "/api/v1/donations/dev-products",
			Method = "POST",
			Headers = headers,
			Body = HttpService:JSONEncode(data),
		})
	end)

	if success and result.Success then -- result.Success indicates if the DynamicDonations API returned a successful response
		local resBody = HttpService:JSONDecode(result.Body)
		local devProductId = resBody.data.devProductId
        local product = MarketplaceService:PromptProductPurchase(user,devProductId)

		-- Prompt the player to purchase the product using MarketplaceService
		-- NOTE: For the purchase to be successful you also need to implement a ProcessReceipt callback
	end
end

For the Process Receipt I would Prefer if this was within another function here is one I have created.

function BankingService:ProcessReceipt(reciept,user: Player,transctiontype: string)

end

Dont mind transactiontype as its for something personal.

So when using ProcessReceipt, Ive never done this before so dont attack me. I went on the docs ( usually a good place to go but im still confused )

Link to ProcessReceipt —> MarketplaceService | Documentation - Roblox Creator Hub

Theres quite alot going on. Could you Help?

P.S I’m not asking you to spoonfeed me

Or Would my function Look like this?

function BankingService:ProcessReceipt(receiptInfo)
    if receiptInfo.ProductId == YOUR_DEV_PRODUCT_ID and receiptInfo.PurchaseId then
        local player = game.Players:GetPlayerByUserId(receiptInfo.PlayerId)
        if player then
            print(player.Name .. " has successfully purchased the dev product!")
        end
    else
        print("Failed to process receipt for product ID: " .. receiptInfo.ProductId)
    end
end

and the call look like:

 if success and result.Success then
        local resBody = HttpService:JSONDecode(result.Body)
        local devProductId = resBody.data.devProductId
        local product = MarketplaceService:PromptProductPurchase(user, devProductId)

        -- Add a callback for processing the receipt
        MarketplaceService.ProcessReceipt = function(receiptInfo)
            self:ProcessReceipt(receiptInfo)
        end
    end

Any Feedback is Appreciated


image

I Reckon it should be something like this:

	local headers = {
		["Content-Type"] = "application/json",
		["api-key"] = API_KEY,
	}

Which also errors and returns a table

Changing my apikey to a string results with no difference

When handling a ProcessReceipt callback you need to return an Enum.ProductPurchaseDecision enum, Enum.ProductPurchaseDecision.NotProcessedYet when it fails and Enum.ProductPurchaseDecision.PurchaseGranted when it succeds, otherwise Roblox will keep calling the ProcessReceipt callback when the player joins indefinitely.

So where you have your print statements add

return Enum.ProductPurchaseDecision.<DecisionType>

Yes, you are correct, I made a typo when writing the tutorial and also forgot that the name for the API key header is x-api-key and not api-key, the latter is why you are getting that HTTP 400 status code.

thank you for the clarification please edit your post for future users

No problem and I already edited the original post when i replied to you!

this should be the code needed i presume

function BankingService:ProcessReceipt(receiptInfo,productid: number,transactiontype: string)
	if receiptInfo.ProductId == productid and receiptInfo.PurchaseId then
		local player = game.Players:GetPlayerByUserId(receiptInfo.PlayerId)
		if player then
			print(player.Name .. " has purchased the product")
		end
        return Enum.ProductPurchaseDecision.PurchaseGranted
	else
		print("Failed to process receipt for product ID: " .. receiptInfo.ProductId)
        return Enum.ProductPurchaseDecision.NotProcessedYet
	end
end

Ill keep testing the Code and let you know if i have any questions

Yes, that code seems to be correct.

1 Like

Ok So im getting this error now:
image

There are possible cases you need to review:

  • The key you are sending doesn’t match the one configured on the server
  • You incorrectly set up the API key on the server. On the render server you need to add an environment variable named API_KEY with the same value that you are sending from the Roblox game.

Can you share your render environment variables configuration while hiding your secrets? If everything appears to be properly set up, try restarting the server through the render dashboard.

Sure I’ll be exposing my key so that if there is a mistake others dont make it ( gonna change it after anyway )

All environment variables’ values are interpreted as strings so you don’t need to add double quotes. If you add double quotes, the value that the server will check for will be apiKey == "\"28958464\"" which will evaluate to false.

i see ill change this and restart my servers