If you run an AI, crypto, geopolitics, or dataâanalysis Discord server, you already know the value of a tightâknit community. What if you could monetize that expertise with a lightweight Python bot that sells a premium product directly inâchannel? In this tutorial youâll learn how to set up a Discord bot on a virtual private server (VPS), integrate Stripe Checkout, and keep everything running smoothly with systemd.
đ What Youâll Build
- A Discord bot (using
nextcord, the modern fork ofdiscord.py) that lives on a VPS. - The bot reads its token from a secure
.envfile. - It posts a nicely formatted embed advertising a âPROâ product, with a Stripe Checkout Session link (test mode first).
- After posting, the bot sleeps a random 10â30âŻmin interval to stay friendly to Discordâs rate limits.
- It monitors for purchase confirmations (via Stripe webhooks) and logs the Discord user ID that bought the product.
- Full error handling for HTTPâŻ429 (rateâlimit) responses and automatic retries.
- Finally, the bot is launched as a systemd service, keeping it alive after reboots or crashes.
đ§ Prerequisites
| Requirement | Why Itâs Needed |
|---|---|
| A VPS (UbuntuâŻ22.04 LTS recommended) â e.g., a $5/month droplet from DigitalOcean | Gives you root access, stable networking, and a place to run the bot 24/7. |
| PythonâŻ3.11+ | Modern syntax, faster runtime, and full typeâhint support. |
| A Discord bot token (create a bot at the Discord Developer Portal) | Allows the script to connect to Discord. |
| A Stripe account (test mode) â signâup at stripe.com | Generates Checkout Sessions for the product youâre selling. |
Domain name (optional) for Stripe webhook endpoint â e.g., bot.yourdomain.com
|
Stripe needs a publicly reachable HTTPS URL to POST webhook events. |
| Git (optional) | For version control and easy deployment. |
1ď¸âŁ Spin Up the VPS & Install System Packages
# 1ď¸âŁ Update & install core tools
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3-pip python3-venv git curl
# 2ď¸âŁ Create a dedicated user (optional but recommended)
sudo adduser --disabled-password --gecos "" discordbot
sudo usermod -aG sudo discordbot # give sudo if you need it
Tip: If you prefer a managed Python environment, consider using
pyenvinstead of the systemvenv.
2ď¸âŁ Clone the Project & Set Up a Virtual Environment
# Switch to the bot user
sudo -i -u discordbot
cd ~
git clone https://github.com/yourusername/discord-stripe-bot.git
cd discord-stripe-bot
# Create a virtualenv
python3 -m venv .venv
source .venv/bin/activate
# Install dependencies
pip install --upgrade pip
pip install nextcord python-dotenv stripe aiohttp
3ď¸âŁ Secure Configuration â .env File
Create a hidden .env file in the project root. This file must never be committed to Git.
# .env
DISCORD_BOT_TOKEN=YOUR_DISCORD_BOT_TOKEN
STRIPE_API_KEY=sk_test_XXXXXXXXXXXXXXXXXXXXXXXX
STRIPE_WEBHOOK_SECRET=whsec_XXXXXXXXXXXXXXXXXXXXXXXX
PRODUCT_PRICE_ID=price_1HhXXXXXXXXXXXXXX # Set in Stripe Dashboard
TARGET_GUILD_IDS=123456789012345678,987654321098765432
TARGET_CHANNEL_IDS=112233445566778899,998877665544332211
Security note: Keep the
.envfile readable only by the bot user:chmod 600 .env.
4ď¸âŁ The Bot â bot.py
Below is a complete, productionâready script. Comments explain each section.
python
#!/usr/bin/env python3
import os
import random
import asyncio
import logging
from datetime import datetime
from pathlib import Path
import nextcord
from nextcord import Embed, Colour
from nextcord.ext import commands, tasks
import stripe
from dotenv import load_dotenv
from aiohttp import web
# -------------------------------------------------
# Load environment variables
# -------------------------------------------------
BASE_DIR = Path(__file__).parent
load_dotenv(BASE_DIR / ".env")
DISCORD_TOKEN = os.getenv("DISCORD_BOT_TOKEN")
STRIPE_API_KEY = os.getenv("STRIPE_API_KEY")
STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET")
PRODUCT_PRICE_ID = os.getenv("PRODUCT_PRICE_ID")
TARGET_GUILD_IDS = [int(i) for i in os.getenv("TARGET_GUILD_IDS", "").split(",") if i]
TARGET_CHANNEL_IDS = [int(i) for i in os.getenv("TARGET_CHANNEL_IDS", "").split(",") if i]
# -------------------------------------------------
# Stripe init
# -------------------------------------------------
stripe.api_key = STRIPE_API_KEY
# -------------------------------------------------
# Logging setup
# -------------------------------------------------
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
handlers=[logging.StreamHandler()],
)
log = logging.getLogger("discord-bot")
# -------------------------------------------------
# Bot definition
# -------------------------------------------------
intents = nextcord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)
# -------------------------------------------------
# Helper: create checkout session
# -------------------------------------------------
def create_checkout_session(user_id: int) -> str:
"""Return a URL to a Stripe Checkout Session (test mode)."""
session = stripe.checkout.Session.create(
success_url="https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url="https://yourdomain.com/cancel",
payment_method_types=["card"],
mode="payment",
line_items=[
{
"price": PRODUCT_PRICE_ID,
"quantity": 1,
}
],
metadata={"discord_user_id": str(user_id)},
)
return session.url
# -------------------------------------------------
# Command: post the product embed
# -------------------------------------------------
@bot.event
async def on_ready():
log.info(f"Logged in as {bot.user} (ID: {bot.user.id})")
# Kick off the periodic posting task
post_product.start()
@tasks.loop(minutes=1) # dummy loop; real timing handled inside
async def post_product():
"""Posts the product embed, then sleeps a random 10â30âŻmin."""
# Choose a random target channel
if not TARGET_CHANNEL_IDS:
log.warning("No target channels defined.")
return
channel_id = random.choice(TARGET_CHANNEL_IDS)
channel = bot.get_channel(channel_id)
if not channel:
log.error(f"Channel ID {channel_id} not found or bot lacks access.")
return
# Build embed
embed = Embed(
title="đ Unlock the PRO AI Toolkit",
description=(
"Gain instant access to premium prompts, exclusive datasets, and a private "
"developer lounge.\n\n"
"**Benefits**\n"
"- Faster model responses\n"
"- Earlyâbeta features\n"
"- Direct support from the creator"
),
colour=Colour.dark_blue(),
timestamp=datetime.utcnow(),
)
embed.set_thumbnail(url="https://i.imgur.com/yourlogo.png")
embed.set_footer(text="Click the button below to purchase â limited spots!")
# Create Checkout link
checkout_url = create_checkout_session(user_id=0) # 0 = generic link
view = nextcord.ui.View()
view.add_item(
nextcord.ui.Button(
label="Buy PRO â $9.99",
style=nextcord.ButtonStyle.link,
url=checkout_url,
)
)
try:
await channel.send(embed=embed, view=view)
log.info(f"Posted embed
Top comments (0)