0%

telegram | stars 支付案例

这里使用 stars 进行简单的支付案例。

可以做到下面的功能

  • 后端追踪支付状态
  • 前端请求支付信息

原理是

  1. 前端发送请求到后端,并附带支付 id
  2. 后端收到请求后,返回支付信息
  3. 前端进入 tg 自己的支付界面,进行支付
  4. 后端接收来自 tg 服务器关于支付的结果
  5. 如果断网后,后端可以根据支付 id 来追踪支付结果,并更新

本案例的后端用 python 编写,并且,我将给出详尽的注释。

  • 前端
    • 域名是 https://tg.thlm.bond
  • 后端
    • 域名是 https://tgapi.thlm.bond
    • 通过 nginxhttp://127.0.0.1:8443 做一个链接

过程:通过点击前端的支付按钮,跳出一个支付面板。

前端

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
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Telegram Stars 支付</title>
<!-- 引入 Telegram WebApp JS -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: var(--tg-theme-bg-color, #ffffff);
color: var(--tg-theme-text-color, #000000);
}

.product-card {
border: 1px solid var(--tg-theme-hint-color, #ddd);
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}

.buy-button {
background-color: var(--tg-theme-button-color, #0088cc);
color: var(--tg-theme-button-text-color, #ffffff);
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
width: 100%;
margin-top: 10px;
}

#debug {
margin-top: 20px;
padding: 10px;
background-color: #f0f0f0;
border-radius: 5px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<div class="product-card">
<h2>商品名称</h2>
<p>价格: 10 Stars</p>
<p>描述: 这是一个测试商品</p>
<button class="buy-button" id="buyButton">购买</button>
</div>

<!-- 添加调试信息区域 -->
<div id="debug"></div>

<script>
// 调试函数
function log(message) {
const debugDiv = document.getElementById('debug');
const timestamp = new Date().toLocaleTimeString();
debugDiv.innerHTML += `[${timestamp}] ${message}\n`;
console.log(message);
}

// 检查 Telegram WebApp 是否存在
if (window.Telegram && window.Telegram.WebApp) {
log('Telegram WebApp 已加载');
const webapp = window.Telegram.WebApp;

// 初始化 WebApp
webapp.ready();
log('WebApp 已初始化');

// 获取初始数据
const initDataUnsafe = webapp.initDataUnsafe || {};
log('初始数据: ' + JSON.stringify(initDataUnsafe));

// 获取用户信息
const user = initDataUnsafe.user || {};
const userId = user.id;
log('用户ID: ' + userId);

// 点击事件处理
document.getElementById('buyButton').addEventListener('click', async function () {
try {
log('按钮被点击');

// 生成支付ID
const paymentId = 'pay_' + Date.now();
log('生成支付ID: ' + paymentId);

// 构建请求数据
const requestData = {
chat_id: userId,
user_id: userId,
product_id: 'test_product',
amount: 1000,
payment_id: paymentId
};
log('请求数据: ' + JSON.stringify(requestData));

const response = await fetch('https://tgapi.thlm.bond/create_payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Telegram-Init-Data': webapp.initData || ''
},
body: JSON.stringify(requestData)
})
const result = await response.json();
log('服务器响应: ' + JSON.stringify(result));

if (result.success && result.invoice_url) {
// 直接打开支付界面
webapp.openInvoice(result.invoice_url, function (status) {
if (status === 'paid') {
log("支付成功")
} else if (status === 'failed') {
log("支付失败")
} else if (status === 'cancelled') {
log("支付取消")
}
});
} else {
throw new Error(result.error || '创建支付失败');
}
} catch (error) {
log("创建支付失败")
}
});

// 设置主题
try {
document.body.style.backgroundColor = webapp.backgroundColor;
document.body.style.color = webapp.textColor;
log('主题已应用');
} catch (e) {
log('应用主题时出错: ' + e.message);
}
} else {
log('错误: Telegram WebApp 未加载');
alert('请在 Telegram WebApp 中打开');
}
</script>
</body>
</html>
  • paymentId 是自己弄的一个支付唯一 id,这个 id 可以追踪支付情况

后端

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import logging
from datetime import datetime

import requests
from flask import Flask, request, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app) # 启用跨域支持

logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# 配置
TELEGRAM_BOT_TOKEN = "-poY"
TELEGRAM_API_URL = f'https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}'

# 简单的内存存储,实际应用中应该使用数据库
payment_records = {}


@app.route('/create_payment', methods=['POST'])
def create_payment():
try:
data = request.json
chat_id = data.get('chat_id') # chatId 是 Telegram 聊天的唯一标识符。
user_id = data.get('user_id') # userId 是 Telegram 用户的唯一标识符。
product_id = data.get('product_id')
amount = data.get('amount')
payment_id = data.get('payment_id') # 从前端接收支付 ID

logger.info(f"payment_id: {payment_id} chat_id: {chat_id} user_id: {user_id}")

if not all([chat_id, user_id, product_id, amount, payment_id]):
return jsonify({
'success': False,
'error': '缺少必要参数'
}), 400

# 存储支付信息
payment_records[payment_id] = {
'chat_id': chat_id,
'user_id': user_id,
'product_id': product_id,
'amount': amount,
'status': 'pending',
'created_at': datetime.utcnow().isoformat()
}

# 创建支付发票
invoice_data = {
'chat_id': chat_id,
'title': '测试商品',
'description': '这是一个测试商品的描述',
'payload': payment_id, # 用于在支付完成后识别和验证支付请求。可以是订单 ID 或其他唯一标识符。
'currency': 'XTR', # Telegram Stars
'prices': [{
'label': '商品价格',
'amount': amount
}],
'start_parameter': payment_id
}

# 发送支付请求到Telegram
response = requests.post(f'{TELEGRAM_API_URL}/createInvoiceLink', json=invoice_data)
result = response.json()

if result.get('ok'):
return jsonify({
'success': True,
'invoice_url': result['result']
})
else:
return jsonify({
'success': False,
'error': result.get('description', '创建发票失败')
})

except Exception as e:
return jsonify({
'success': False,
'error': str(e)
}), 500


@app.route('/webhook', methods=['POST'])
def webhook():
try:
data = request.json

# 处理预支付查询
if 'pre_checkout_query' in data:
logger.info(f"pre_checkout_query")
pre_checkout_query = data['pre_checkout_query']
query_id = pre_checkout_query['id']
payment_id = pre_checkout_query['invoice_payload']

logger.info(f"query_id: {query_id} payment_id: {payment_id}")

# 验证支付记录
payment_info = payment_records.get(payment_id)
if not payment_info:
# 如果找不到支付记录,拒绝支付
response = requests.post(f'{TELEGRAM_API_URL}/answerPreCheckoutQuery', json={
'pre_checkout_query_id': query_id,
'ok': False,
'error_message': '找不到支付记录'
})
return '', 200

# 验证通过,允许继续支付
response = requests.post(f'{TELEGRAM_API_URL}/answerPreCheckoutQuery', json={
'pre_checkout_query_id': query_id,
'ok': True
})

logger.info(f"通过预支付")

# 更新支付状态
payment_records[payment_id]['status'] = 'pre_checkout_approved'

# 处理成功支付
elif 'message' in data and 'successful_payment' in data['message']:
logger.info(f"successful_payment")
successful_payment = data['message']['successful_payment']
payment_id = successful_payment['invoice_payload']
logger.info(f"successful_payment: {successful_payment} payment_id: {payment_id}")

# 更新支付状态
if payment_id in payment_records:
payment_records[payment_id]['status'] = 'completed'
payment_records[payment_id]['completed_at'] = datetime.utcnow().isoformat()

# 这里可以添加其他支付成功后的处理逻辑
# 例如:发送确认消息、更新数据库、触发商品发送等

return '', 200

except Exception as e:
logger.error(f"Webhook error: {str(e)}")
return '', 500


def setup_webhook():
"""设置 Webhook"""
# 1. 删除现有的 webhook
requests.post(f"{TELEGRAM_API_URL}/deleteWebhook")

# 2. 设置新的 webhook
webhook_url = "https://tgapi.thlm.bond/webhook"
set_url = f"{TELEGRAM_API_URL}/setWebhook"
set_payload = {
"url": webhook_url,
"allowed_updates": ["message", "pre_checkout_query", "callback_query"]
# "allowed_updates": []
}
response = requests.post(set_url, json=set_payload)
logger.info("Webhook 设置结果:", response.json())

return response.json()


if __name__ == '__main__':
setup_webhook()
app.run(host='127.0.0.1', port=8443)
请我喝杯咖啡吧~