#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import uuid
import logging
import requests
from telegram import (
InlineKeyboardButton,
InlineKeyboardMarkup,
Update,
BotCommand,
)
from telegram.ext import (
ApplicationBuilder,
CommandHandler,
CallbackQueryHandler,
MessageHandler,
filters,
ContextTypes,
)
from dotenv import load_dotenv
# ── LOAD ENV ─────────────────────────────────────────────────────────────────
load_dotenv()
TELEGRAM_TOKEN = os.getenv("TGPayment_TOKEN") # Bot token from BotFather
NOWPAYMENTS_KEY = os.getenv("NOW_API_KEY") # Your production NOWPayments key
NOWPAYMENTS_URL = "https://api.nowpayments.io/v1"
CHECK_INTERVAL = 60 # seconds between polls
# Channel invite targets (bot must be admin with invite rights)
CHANNEL_A = os.getenv("Channel_A") # $5 package
CHANNEL_B = os.getenv("Channel_B") # $10 package
CHANNEL_C = os.getenv("Channel_C") # $15 package
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Track pending payments: { payment_id: (chat_id, amount) }
pending = {}
# ── MENU COMMANDS ─────────────────────────────────────────────────────────────
async def set_menu_commands(app):
await app.bot.set_my_commands([
BotCommand("start", "Show guide and menu"),
BotCommand("pay", "Choose your payment package"),
])
# ── GUIDE HANDLER ─────────────────────────────────────────────────────────────
async def show_guide(update: Update, context: ContextTypes.DEFAULT_TYPE):
guide = (
"👋 *Welcome to CryptoPayBot!*\n\n"
"1️⃣ Send `/pay` to pick a package ($5, $10, $15).\n"
"2️⃣ Pay in *any* crypto; you get USDC.\n"
"3️⃣ On confirmation, receive a channel invite link.\n\n"
"Enjoy! 🚀"
)
await update.message.reply_markdown(guide)
# ── INVOICE CREATION ───────────────────────────────────────────────────────────
def create_invoice(amount_usd: float, chat_id: int):
order_id = f"{chat_id}-{uuid.uuid4().hex[:6]}"
payload = {
"price_amount": amount_usd,
"price_currency": "usd",
"pay_currency": "btc", # specify which coin user pays in
"order_id": order_id,
"order_description": f"Access Package - ${amount_usd}",
"ipn_callback_url": "https://yourdomain.com/ipn",
"is_fee_paid_by_user": True
}
headers = {
"x-api-key": NOWPAYMENTS_KEY,
"Content-Type": "application/json"
}
resp = requests.post(f"{NOWPAYMENTS_URL}/payment", json=payload, headers=headers)
resp.raise_for_status()
data = resp.json()
pending[data["payment_id"]] = (chat_id, amount_usd)
return data["payment_id"], data["payment_url"]
# ── COMMAND: /start ────────────────────────────────────────────────────────────
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await show_guide(update, context)
# ── COMMAND: /pay ──────────────────────────────────────────────────────────────
async def pay(update: Update, context: ContextTypes.DEFAULT_TYPE):
buttons = [
[InlineKeyboardButton("$5", callback_data="pkg_5")],
[InlineKeyboardButton("$10", callback_data="pkg_10")],
[InlineKeyboardButton("$15", callback_data="pkg_15")],
]
await update.message.reply_text("Choose your package:", reply_markup=InlineKeyboardMarkup(buttons))
# ── CALLBACK: package selected ─────────────────────────────────────────────────
async def package_chosen(update: Update, context: ContextTypes.DEFAULT_TYPE):
q = update.callback_query
await q.answer()
chat_id = q.message.chat.id
amount = float(q.data.split("_")[1])
_, url = create_invoice(amount, chat_id)
await q.message.reply_markdown(f"🔗 Pay *${amount}* here:\n{url}\n\nI’ll notify you once confirmed.")
# ── POLLING JOB: check payments ─────────────────────────────────────────────────
async def poll_payments(context: ContextTypes.DEFAULT_TYPE):
headers = {"x-api-key": NOWPAYMENTS_KEY}
to_remove = []
for pid, (chat_id, amount) in pending.items():
resp = requests.get(f"{NOWPAYMENTS_URL}/payment_status", params={"payment_id": pid}, headers=headers)
resp.raise_for_status()
if resp.json().get("payment_status") == "finished":
await context.bot.send_message(chat_id, "✅ Payment received!")
target = CHANNEL_A if amount == 5 else CHANNEL_B if amount == 10 else CHANNEL_C
invite = await context.bot.export_chat_invite_link(target)
await context.bot.send_message(chat_id, f"🎉 Join here:\n{invite}")
to_remove.append(pid)
for pid in to_remove:
pending.pop(pid, None)
# ── MAIN ENTRYPOINT ────────────────────────────────────────────────────────────
def main():
app = (
ApplicationBuilder()
.token(TELEGRAM_TOKEN)
.post_init(set_menu_commands) # register menu once
.build()
)
# catch-all guide on plain text only
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, show_guide), group=0)
# core bot commands and callbacks
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("pay", pay))
app.add_handler(CallbackQueryHandler(package_chosen, pattern="^pkg_"))
# schedule the polling job
app.job_queue.run_repeating(poll_payments,
interval=CHECK_INTERVAL,
first=CHECK_INTERVAL)
# run long-polling without signal hooks and without closing loop :contentReference[oaicite:2]{index=2}:contentReference[oaicite:3]{index=3}
app.run_polling(stop_signals=None, close_loop=False)
if __name__ == "__main__":
main()