Telegram Bot 无法接收 preCheckoutQuery update
结论
pre_checkout_query 被发送到了当前登记的 webhook:
https://telegram.botframework.com/api/telegram/...
它并没有发送到你的 UpdateController。Telegram 对每个 bot token 只保留一个 webhook,无法同时把普通消息交给 Bot Framework,再把支付更新发往另一个地址。
要接收 pre_checkout_query,需要将 Telegram 的 webhook 设为你自己的公网 HTTPS 地址,并在收到更新后及时调用 answerPreCheckoutQuery。如果继续使用 Bot Framework 的 Telegram Channel,能否处理支付更新取决于连接器是否支持并转发 pre_checkout_query。结合 HTTP 400 和控制器始终收不到请求这两个现象,这类更新很可能在 Bot Framework 的入口处就被拒绝了。
为什么普通消息正常,支付更新却失败
当前的数据流是:
Telegram
→ telegram.botframework.com
→ Bot Framework
→ 你的 Bot Framework endpoint
getWebhookInfo 返回的 URL 表明,Telegram 并未直接调用你的控制器:
{
"ok": true,
"result": {
"url": "https://telegram.botframework.com/api/telegram/XXX/XXX",
"pending_update_count": 0,
"max_connections": 40
}
}
普通消息能够正常收发,只能说明 Bot Framework Telegram Channel 可以处理这些消息,不能说明它支持 Telegram Payments 的所有 update 类型。
pending_update_count 偶尔变为 1,通常说明有一个更新尚未成功投递。Bot Framework 返回 HTTP 400 后,Telegram 会暂时保留该更新并重试。这和 TLS 1.2 没有关系,因为请求已经到达 Bot Framework,BadRequest 也是由它返回的。
正确的处理方式
1. 将 webhook 改为自己的地址
你的地址需要满足以下条件:
- 可通过公网访问;
- 使用有效的 HTTPS 证书;
- 路由与 ASP.NET Controller 完全匹配;
- 能接收 Telegram Bot API 的原始
UpdateJSON; - 成功处理后返回 HTTP 200。
例如:
https://example.com/api/update
可以使用 Telegram.Bot 设置 webhook。不同版本的 Telegram.Bot 在方法名和参数形式上可能略有区别,核心写法如下:
await botClient.SetWebhookAsync(
url: "https://example.com/api/update",
allowedUpdates: new[]
{
UpdateType.Message,
UpdateType.PreCheckoutQuery
});
也可以直接调用 Bot API:
curl -X POST "https://api.telegram.org/bot<BOT_TOKEN>/setWebhook" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/api/update",
"allowed_updates": ["message", "pre_checkout_query"]
}'
设置后再次检查:
curl "https://api.telegram.org/bot<BOT_TOKEN>/getWebhookInfo"
返回结果中的 result.url 必须是你自己的地址,不能再是:
https://telegram.botframework.com/...
改用自己的 webhook 后,Bot Framework Telegram Channel 将不再直接收到这个 bot 的 Telegram updates。
2. 在控制器中处理 PreCheckoutQuery
下面的代码展示了基本处理逻辑。具体名称可能因 Telegram.Bot 版本而异,但处理步骤相同:
using Microsoft.AspNetCore.Mvc;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
[ApiController]
[Route("api/update")]
public class UpdateController : ControllerBase
{
private readonly ITelegramBotClient _botClient;
public UpdateController(ITelegramBotClient botClient)
{
_botClient = botClient;
}
[HttpPost]
public async Task<IActionResult> Post(
[FromBody] Update update,
CancellationToken cancellationToken)
{
if (update.Type == UpdateType.PreCheckoutQuery &&
update.PreCheckoutQuery is not null)
{
var query = update.PreCheckoutQuery;
bool paymentCanProceed = ValidateOrder(
query.InvoicePayload,
query.Currency,
query.TotalAmount);
if (paymentCanProceed)
{
await _botClient.AnswerPreCheckoutQueryAsync(
preCheckoutQueryId: query.Id,
cancellationToken: cancellationToken);
}
else
{
await _botClient.AnswerPreCheckoutQueryAsync(
preCheckoutQueryId: query.Id,
errorMessage: "订单信息已失效,请重新发起支付。",
cancellationToken: cancellationToken);
}
}
return Ok();
}
private static bool ValidateOrder(
string invoicePayload,
string currency,
int totalAmount)
{
// 根据 invoicePayload 查找服务端订单,
// 并校验币种、金额和订单状态。
return true;
}
}
有些 Telegram.Bot 版本使用不带 Async 后缀的方法,例如:
await _botClient.AnswerPreCheckoutQuery(
preCheckoutQueryId: query.Id,
cancellationToken: cancellationToken);
请以项目实际安装版本的 API 为准,不要混用不同版本的示例。
必须及时确认预结账请求
收到 pre_checkout_query 后,bot 必须尽快调用:
answerPreCheckoutQuery
Telegram 对确认时间有严格限制,通常要求在 10 秒内完成。因此,不要在确认前执行耗时任务,例如:
- 长时间数据库事务;
- 调用响应很慢的第三方服务;
- 生成文件;
- 等待人工确认。
订单校验应尽量简短。接受支付时,不要提供 error_message;拒绝支付时,则应提供用户能够理解的错误说明。
还要处理支付成功消息
answerPreCheckoutQuery 仅表示允许 Telegram 继续支付流程,并不代表款项已经支付成功。最终结果应从消息中的 successful_payment 获取:
if (update.Message?.SuccessfulPayment is { } payment)
{
string invoicePayload = payment.InvoicePayload;
string currency = payment.Currency;
int totalAmount = payment.TotalAmount;
string telegramPaymentChargeId = payment.TelegramPaymentChargeId;
string providerPaymentChargeId = payment.ProviderPaymentChargeId;
// 使用 invoicePayload 查找订单,并以幂等方式标记为已支付。
}
业务逻辑应以 successful_payment 作为最终支付依据,同时保证重复 update 不会导致重复发货或重复记账。
如果必须保留 Bot Framework
如果现有系统仍需依赖 Bot Framework,可以让自建 webhook 成为 Telegram 的唯一入口:
Telegram
→ 自建 webhook
├─ 处理 pre_checkout_query 和 successful_payment
└─ 将受支持的普通消息交给现有业务层
不要尝试多次调用 setWebhook 来配置两个地址,因为后一次设置会覆盖前一次设置。
你也可以确认当前使用的 Bot Framework Telegram Channel 是否明确支持 Telegram Payments。如果连接器不支持 pre_checkout_query,修改 ASP.NET Controller、TLS 设置或 Telegram.Bot 的反序列化配置都无法解决问题,因为更新根本没有到达你的应用。
排查要点
按以下顺序检查:
getWebhookInfo.result.url是否为自己的 webhook。allowed_updates是否包含pre_checkout_query,也可以不限制 update 类型。- ASP.NET 路由是否与 webhook URL 一致。
- Controller 参数中的
Update是否来自Telegram.Bot.Types。 - webhook 是否向 Telegram 返回 HTTP 200。
- 是否及时调用了
answerPreCheckoutQuery。 getWebhookInfo中是否出现last_error_message、last_error_date,或者pending_update_count是否持续增长。- 是否仍有 Bot Framework 或部署脚本在重新设置 webhook。
问题的核心不是 ServicePointManager.SecurityProtocol,而是 webhook 当前属于谁。只要 getWebhookInfo 仍指向 telegram.botframework.com,Telegram 就不会直接调用你的 UpdateController。
备注:内容仅供参考。
