Py.Cafe

Wayne988278/

streamlit-on-pycafe

Streamlit on Py.cafe

DocsPricing
  • .env
  • app.py
  • requirements.txt
  • tgpayment.py
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#!/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()