name: telegram-bot-payments description: Add paywall to OpenClaw Telegram bots. Covers Stripe external link (94% margin), Telegram Stars (65% margin, required for iOS), and TON Wallet Pay (99% margin). Includes webhook server, credits system, and AGENTS.md behavior. license: MIT homepage: https://canlah.ai metadata: author: Canlah AI version: "1.0.2" tags: ["telegram", "payments", "stripe", "stars", "openclaw", "monetization"] allowed-tools: Bash Read Edit Write Grep Glob Agent user-invocable: true
Add paid credits to any OpenClaw Telegram bot. Supports three payment methods with a hybrid approach to maximize developer margin.
| Method | User pays $10 → You receive | When to use |
|---|---|---|
| Stripe (external link) | ~$9.41 (94%) | Android, Desktop, Web users — best margin |
| TON Wallet Pay | ~$9.90 (99%) | Crypto-savvy users — best margin, zero fees |
| Telegram Stars | ~$6.50 (65%) | iOS users ONLY — legally required for in-app digital goods on iOS |
Stars = two fees back-to-back:
User pays $10
→ Apple/Google takes ~30% (mandatory IAP)
→ Telegram takes ~5%
→ You receive ~$6.50
For image generation at $0.03–$0.08/image, Stars makes most packages unprofitable.
User on iOS? → Stars (no choice — Apple policy)
User on Android/Desktop/Web? → Stripe external link (9x better margin)
Telegram does NOT currently ban external payment links. Risk is low-medium. If enforced in future, fall back to Stars or TON.
7w4.net收錄了海量優質技能外掛。
Quota exhausted
↓
Agent detects (check_quota.py returns allowed=false)
↓
Agent sends payment options message + buttons
↓
├─ [Pay with Card ⭐] → Stripe Payment Link (external browser)
├─ [Pay with Stars ⭐] → Telegram Stars invoice (iOS compliance)
└─ [Pay with TON 💎] → TON Wallet Pay link (optional)
↓
Payment completed → Stripe/Stars webhook fires
↓
payment_server.py updates /workspaces/{USER_ID}/usage.json credits
↓
User continues generating
In Stripe Dashboard → Payment Links → Create:
Product: "20 Image Credits" Price: $2.00 → copy link e.g. https://buy.stripe.com/xxx
Product: "50 Image Credits" Price: $4.00 → copy link e.g. https://buy.stripe.com/yyy
Product: "100 Image Credits" Price: $7.00 → copy link e.g. https://buy.stripe.com/zzz
Critical: Add a metadata field telegram_user_id — but Payment Links don't support dynamic metadata natively. Two options:
Option A (Simple) — URL with client_reference_id:
Stripe Payment Links support ?client_reference_id=USER_ID as a query param. Append the user's Telegram ID:
https://buy.stripe.com/xxx?client_reference_id=697391377
The client_reference_id appears in the webhook payload.
Option B (Proper) — Stripe Checkout Session:
Create a dynamic session per user via API (see create_checkout.py below). More control, supports pre-filling email, adding metadata.
payment_server.py (FastAPI, runs on port 8001)"""
Unified payment webhook server.
Handles: Stripe webhooks + Telegram Stars pre_checkout_query + successful_payment
Run: uvicorn payment_server:app --host 0.0.0.0 --port 8001
"""
import json
import os
import pathlib
import httpx
from fastapi import FastAPI, Request, HTTPException
import stripe
app = FastAPI()
WORKSPACES = pathlib.Path(os.environ.get("WORKSPACES_DIR", "/workspaces"))
BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
STRIPE_WEBHOOK_SECRET = os.environ.get("STRIPE_WEBHOOK_SECRET", "")
stripe.api_key = os.environ.get("STRIPE_SECRET_KEY", "")
CREDIT_PACKAGES = {
"price_20": {"credits": 20, "price_id": "price_xxx"}, # replace with real Stripe price IDs
"price_50": {"credits": 50, "price_id": "price_yyy"},
"price_100": {"credits": 100, "price_id": "price_zzz"},
}
def add_credits(user_id: str, credits: int):
"""Add credits to user's usage.json."""
usage_file = WORKSPACES / user_id / "usage.json"
if usage_file.exists():
usage = json.loads(usage_file.read_text())
else:
usage = {"daily_count": 0, "credits": 0, "tier": "free"}
usage["credits"] = usage.get("credits", 0) + credits
usage_file.write_text(json.dumps(usage, indent=2))
return usage["credits"]
def notify_user(user_id: str, credits_added: int, total_credits: int):
"""Send Telegram message to user after successful payment."""
httpx.post(
f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
json={
"chat_id": user_id,
"text": f"✅ 充值成功!\n\n+{credits_added} 張圖片額度\n剩餘總額度:{total_credits} 張\n\n直接告訴我你想要什麼吧 👇",
},
timeout=10,
)
# ── Stripe Webhook ──────────────────────────────────────────────────────────
@app.post("/webhook/stripe")
async def stripe_webhook(request: Request):
payload = await request.body()
sig = request.headers.get("stripe-signature", "")
try:
event = stripe.Webhook.construct_event(payload, sig, STRIPE_WEBHOOK_SECRET)
except stripe.error.SignatureVerificationError:
raise HTTPException(status_code=400, detail="Invalid signature")
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
user_id = session.get("client_reference_id") or session.get("metadata", {}).get("telegram_user_id")
credits = int(session.get("metadata", {}).get("credits", 0))
if user_id and credits:
total = add_credits(user_id, credits)
notify_user(user_id, credits, total)
return {"ok": True}
# ── Telegram Stars Webhook ───────────────────────────────────────────────────
@app.post("/webhook/telegram")
async def telegram_webhook(request: Request):
data = await request.json()
# Must answer pre_checkout_query within 10 seconds
pq = data.get("pre_checkout_query")
if pq:
httpx.post(
f"https://api.telegram.org/bot{BOT_TOKEN}/answerPreCheckoutQuery",
json={"pre_checkout_query_id": pq["id"], "ok": True},
timeout=8,
)
return {"ok": True}
msg = data.get("message", {})
payment = msg.get("successful_payment")
if payment:
# payload format: "credits_20_697391377"
parts = payment.get("invoice_payload", "").split("_")
if len(parts) == 3 and parts[0] == "credits":
credits = int(parts[1])
user_id = parts[2]
total = add_credits(user_id, credits)
notify_user(user_id, credits, total)
return {"ok": True}
buy_credits.py (agent calls this to send payment options)#!/usr/bin/env python3
"""
Send payment options to user when quota is exhausted.
Usage: python3 buy_credits.py <user_id> [stars|stripe|both]
"""
import sys
import os
import httpx
BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
USER_ID = sys.argv[1]
MODE = sys.argv[2] if len(sys.argv) > 2 else "both"
# Replace these with your actual Stripe Payment Links
STRIPE_LINKS = {
"20": "https://buy.stripe.com/xxx?client_reference_id=" + USER_ID,
"50": "https://buy.stripe.com/yyy?client_reference_id=" + USER_ID,
"100": "https://buy.stripe.com/zzz?client_reference_id=" + USER_ID,
}
# Stars packages (currency XTR, 1 star ≈ $0.013 received by developer)
STARS_PACKAGES = {
"20": {"stars": 200, "credits": 20}, # ~$2.60 retail → ~$1.69 to you
"50": {"stars": 450, "credits": 50}, # ~$5.85 retail → ~$3.80 to you
"100": {"stars": 800, "credits": 100}, # ~$10.40 retail → ~$6.76 to you
}
if MODE in ("stripe", "both"):
# Send external link buttons
httpx.post(
f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
json={
"chat_id": USER_ID,
"text": "今天的免費額度用完了!\n\n💳 用銀行卡充值(推薦):",
"reply_markup": {
"inline_keyboard": [
[{"text": "20張 $2.00", "url": STRIPE_LINKS["20"]},
{"text": "50張 $4.00", "url": STRIPE_LINKS["50"]}],
[{"text": "100張 $7.00 🔥", "url": STRIPE_LINKS["100"]}],
]
},
},
timeout=10,
)
if MODE in ("stars", "both"):
# Send Stars invoice for the most popular package
httpx.post(
f"https://api.telegram.org/bot{BOT_TOKEN}/sendInvoice",
json={
"chat_id": USER_ID,
"title": "50張圖片額度",
"description": "購買50張圖片生成額度,不過期",
"payload": f"credits_50_{USER_ID}",
"currency": "XTR",
"prices": [{"label": "50張圖片", "amount": STARS_PACKAGES["50"]["stars"]}],
},
timeout=10,
)
print("PAYMENT_OPTIONS_SENT")
entrypoint.sh additions# Install dependencies
pip3 install fastapi uvicorn stripe httpx
# Start payment server in background
uvicorn payment_server:app --host 0.0.0.0 --port 8001 &
# Register Telegram webhook for Stars payments
python3 - << 'EOF'
import httpx, os
token = os.environ["TELEGRAM_BOT_TOKEN"]
# Replace with your server's public IP/domain
server = os.environ.get("SERVER_PUBLIC_URL", "https://your-server.com")
httpx.post(
f"https://api.telegram.org/bot{token}/setWebhook",
json={"url": f"{server}/webhook/telegram"}
)
print("Telegram webhook set")
EOF
AGENTS.md payment behavior## 付費引導(額度用完時)
當 check_quota.py 輸出 `allowed=false` 時:
1. 如果本次請求已生成圖片,先發出圖片
2. 執行:exec python3 /workspaces-shared/skills/payments/buy_credits.py {PEER_ID} both
3. 不要多說,讓按鈕說話
嚴格禁止:
- 不要解釋定價邏輯
- 不要提到 Stripe、Stars 是什麼
- 不要說「我無法生成了」——說「今天的免費額度用完了」
文字聊天、問題回答即使額度=0也繼續正常進行。只有生成圖片時才觸發付費引導。
□ Stripe 賬號建立並驗證身份(需要護照/ID)
□ 建立三個 Payment Links(20/50/100張)
□ 每個 Product 的 metadata 里加 credits 欄位(如 credits=20)
□ Webhook endpoint 新增:https://your-server/webhook/stripe
□ 選擇監聽事件:checkout.session.completed
□ 複製 Webhook Signing Secret → 填入 STRIPE_WEBHOOK_SECRET env var
□ 測試:用 Stripe test card 4242 4242 4242 4242 付款,確認 credits 更新
Goal: cover API cost + profit. Using Seedream ($0.03/image):
| Package | Price | Stripe到手 | 成本(Seedream) | 利潤 | 利潤率 |
|---|---|---|---|---|---|
| 20張 | $2.00 | $1.41 | $0.60 | $0.81 | 57% |
| 50張 | $4.00 | $3.41 | $1.50 | $1.91 | 56% |
| 100張 | $7.00 | $6.41 | $3.00 | $3.41 | 53% |
Using NB2 ($0.08/image) — not recommended for paid packages, only free tier:
| Package | Price | Stripe到手 | 成本(NB2) | 利潤 | 利潤率 |
|---|---|---|---|---|---|
| 20張 | $2.00 | $1.41 | $1.60 | -$0.19 | 虧損 |
| 50張 | $4.00 | $3.41 | $4.00 | -$0.59 | 虧損 |
結論:付費包只用 Seedream。NB2 限制在免費試用。
From bot-ux-checklist.md:
✅ 先出圖,再提示額度用完(軟 paywall)
✅ 漸進式提示:「還剩2張」→「最後1張」→「用完了,充值繼續」
✅ 即使額度=0,文字對話繼續正常進行
✅ 付款成功後立即發確認訊息(payment_server 裡已實現)
❌ 不要在生成前彈付款提示
❌ 不要重複推送付款按鈕
Stripe Payment Links 無法動態注入 metadata — 用 client_reference_id 傳 user ID。如果需要 credits 數量,在 Stripe Dashboard 的 Product metadata 裡提前設好,webhook 裡從 line_items 讀取。
Telegram webhook 和 OpenClaw 不能共用同一個 port — OpenClaw 用 8815,payment server 用 8001,分開。
setWebhook 會覆蓋 OpenClaw 的 Telegram 輪詢 — 如果 OpenClaw 用 long-polling(預設),設了 webhook 後 OpenClaw 會收不到訊息。解決:OpenClaw 配置 webhook 模式,或者 payment server 用獨立 bot token(推薦建立一個獨立的 @YourBotPaymentBot 專門處理支付回撥)。
中國使用者 — Stripe 支援中國發行的 Visa/Mastercard,但不支援支付寶/微信支付(需要 Stripe China 賬號)。如果主要是中國使用者:考慮用 TON 或接入 Creem(支援更多支付方式)。
Stars 21天 hold — 提現要等21天,且只能提 TON。Stars 做為 iOS 合規保底,不作為主要收入。
Canlah AI — Run performance marketing without breaking your brand.
這個 Skill 質量中等偏上。優點是功能覆蓋全面,文件清晰易懂,包含完整的實現流程和實用的已知問題說明,定價策略分析對開發者很有幫助。不足之處是內容相對基礎,缺少配置檔案和示例專案,部分安全配置說明不夠詳細,存在少量錯別字。對於需要快速為 Telegram 機器人接入支付功能的開發者來說,是一個可用的參考模板,但建議配合官方文件補充安全相關的配置細節。