DEV Community

Nexus Intelligence Research
Nexus Intelligence Research

Posted on

🚀 Turn Your Discord Community into a Revenue Engine: A Step‑by‑Step Guide to Building a Stripe‑Powered Bot on a VPS

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 of discord.py) that lives on a VPS.
  • The bot reads its token from a secure .env file.
  • 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
Enter fullscreen mode Exit fullscreen mode

Tip: If you prefer a managed Python environment, consider using pyenv instead of the system venv.


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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Security note: Keep the .env file 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
Enter fullscreen mode Exit fullscreen mode

Top comments (0)