Что мы строим и что пропускаем
В этом руководстве показан минимальный путь PHP для принятия Telegram Stars через Bot API. Вы будете:
- Отправить счет
sendInvoiceв валюте.XTR. - Рукоятка;
pre_checkout_queryи звонитеanswerPreCheckoutQueryin time - Получить
message.successful_paymentи подтвердите идентификатор списания, сумму Stars и полезную нагрузку счета. - Избегайте распространенных ошибок: отвечайте слишком поздно, относитесь к Stars как к банковской наличности и доверяйте итоговым показателям на стороне клиента.
Мы не покрываем KYC, снятие фрагментов или налоги. Это уровень интеграции, а не финансовый уровень.
1. Отправка счета в XTR
В счетах Stars используется валюта XTRСЕРП provider_token — Telegram отвергает его для Звезд. Пользователь платит со своего баланса Stars. Ваш полезная нагрузка непрозрачная строка (до 128 байт), которая связывает оплата успешна вернемся к вашему заказу.
<?php
// send_invoice.php — start a Stars checkout
$token = getenv('BOT_TOKEN');
$chatId = (int) $update['message']['chat']['id'];
$payload = bin2hex(random_bytes(7)); // order key, ≤128 bytes
$stars = 1; // integer only
// Persist the order BEFORE sendInvoice
$orderId = saveOrder([
'payload' => $payload,
'chat_id' => $chatId,
'stars' => $stars,
'status' => 'pending',
'created_at' => gmdate('c'),
]);
$body = [
'chat_id' => $chatId,
'title' => 'Pro plan (1 month)',
'description' => 'Unlocks Pro features for 30 days.',
'payload' => $payload,
'currency' => 'XTR',
'prices' => json_encode([['label' => 'Pro 1m', 'amount' => $stars]]),
// provider_token must be OMITTED for XTR
];
$ch = curl_init('https://api.telegram.org/bot'.$token.'/sendInvoice');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($body),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$resp = json_decode($raw, true);
if ($status !== 200 || !($resp['ok'] ?? false)) {
markOrderFailed($orderId, $raw);
throw new RuntimeException('sendInvoice failed: '.$raw);
}
настоящим Правилам
prices[].amountцелые Звезды — без дробей.полезная нагрузкаявляется только ключом корреляции. Не вносите в него фальсифицируемую цену.- Сохранить в первую очередь, отправить во вторую. Если
sendInvoiceне удается, отметьте, что заказ не удался, чтобы вы никогда не начисляли плату, которую вы не можете найти.
2. Ответ на pre_checkout_query
Когда пользователь подтвердит, Telegram отправит pre_checkout_query На своем invoice_payload и Итоговая цена. У вас есть около 10 сек. вызывать answerPreCheckoutQuery. Тайм-аут и Telegram отменяет платеж. Проверьте цену и доступность здесь — никогда не доверяйте данным обратного вызова с клавиатуры за деньги.
<?php
// pre_checkout.php
$token = getenv('BOT_TOKEN');
$pcq = $update['pre_checkout_query'];
$pcqId = $pcq['id'];
$payload = $pcq['invoice_payload'];
$totalStars = (int) $pcq['total_amount'];
$order = loadOrderByPayload($payload);
$ok = $order
&& $order['status'] === 'pending'
&& (int) $order['stars'] === $totalStars
&& empty($order['expires_at_gmt']); // optional TTL check
$body = [
'pre_checkout_query_id' => $pcqId,
'ok' => $ok ? 'true' : 'false',
];
if (!$ok) {
$body['error_message'] = 'This order is no longer available.';
}
$ch = curl_init('https://api.telegram.org/bot'.$token.'/answerPreCheckoutQuery');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($body),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$raw = curl_exec($ch);
curl_close($ch);
$resp = json_decode($raw, true);
if (!($resp['ok'] ?? false)) {
error_log('answerPreCheckoutQuery failed: '.$raw);
}
Проверяет, что важно:
invoice_payloadдолжен решиться на реальный заказ.Итоговая ценадолжны соответствовать звездам, которые вы сохранили.- Статус все еще должен быть
в ожидании. Второй запрос для уже утвержденной полезной нагрузки - повторное воспроизведение — отклонить его.
3. Подтверждение Successful_PAYMENT
После зарядки ваш веб-перехватчик получает message.successful_payment. Этот блок включает в себя:
currency === 'XTR'Итоговая цена— Звезды заряжены (целое число)invoice_payload— строка, которую вы указали в счетеtelegram_payment_charge_id— уникальный идентификатор заряда; хранить егоprovider_payment_charge_id— пусто для звезд; не предполагайте, что он существует
Рассматривайте это обновление только как доказательство получения звезд. Звезды - это баланс на стороне Telegram, а не банковский депозит. Чтобы проверить заработок бота, позвоните getStarTransactions.
<?php
// successful_payment.php
$token = getenv('BOT_TOKEN');
$sp = $update['message']['successful_payment'];
$payload = $sp['invoice_payload'];
$totalStars = (int) $sp['total_amount'];
$currency = $sp['currency'];
$chargeId = $sp['telegram_payment_charge_id'];
$userId = (int) $update['message']['from']['id'];
if ($currency !== 'XTR') {
error_log('Unexpected currency on successful_payment: '.json_encode($sp));
http_response_code(200);
exit;
}
if (alreadyCredited($chargeId)) {
http_response_code(200);
exit;
}
$pdo->beginTransaction();
try {
$order = loadOrderByPayloadForUpdate($payload); // SELECT ... FOR UPDATE
if (!$order || $order['status'] !== 'pending') {
throw new RuntimeException('Order not pending for payload '.$payload);
}
if ((int) $order['stars'] !== $totalStars) {
throw new RuntimeException('Stars mismatch on payload '.$payload);
}
creditUser($userId, $order);
markOrderPaid($order['id'], $chargeId);
recordCharge($chargeId, $order['id']); // unique index on charge_id
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
error_log('successful_payment error: '.$e->getMessage());
// Still return 200 — Telegram will retry on errors. Reconcile later.
}
http_response_code(200);
Проверьте все это:
currency === 'XTR'invoice_payloadсоотносится с отложенным заказомИтоговая ценасоответствует ожидаемым звездамtelegram_payment_charge_idуникален в вашей книге (ключ идемпотентности)
После зачисления вы можете отправить короткий SendMessage подтверждение
4. Производственные заметки
- Секретный код webhook-а. Испытание пройдено
секретный токенотsetWebhookДобавить и подтвердитьX-Telegram-Bot-Api-Secret-Token. - Обновить идемпотентность. Продолжать
update_idи пропускать дубликаты, чтобы веб-перехватчики не могли дважды кредитовать. - Возвраты Относитесь к возвратам Stars как к своим собственным событиям с идентификаторами списаний и примиряйтесь, как к любому реверсу.
- Ведение журнала Держите RAW
оплата успешнаJSON не менее 90 дней. - Вывод средств Звезды не фиатные. Используйте
getStarTransactionsи Fragment for payout — не обещайте пользователям, что оплата равна вашему банковскому депозиту.
Типичные ошибки
- Возврат http 200 на веб-перехватчик без вызова
answerPreCheckoutQuery— Telegram по-прежнему отменяется после тайм-аута. - Чтение цены с кнопки «Купить»
callback_query. Деньги живут вpre_checkout_queryиоплата успешна. - Внесение цены внутрь
полезная нагрузка. Держите его непрозрачным; пересчитайте на сервере. - Включая
provider_tokenдляXTR—sendInvoice- - Обращение со Звездами как с наличными в банке.
- Возврат ошибок на
оплата успешнанавсегда. Журнал, возврат200, и охранник с уникальнымtelegram_payment_charge_id.
6. Сквозная форма
- Пользователь нажимает Купить. Вы
answerCallbackQueryи звонитеsendInvoiceгдеcurrency=XTR. - Telegram показывает лист Stars. При подтверждении отправляет
pre_checkout_query; вы отвечаете только после повторной проверки заказа. - На заряд, Telegram отправляет
оплата успешна. Вы подтверждаете валюту, сумму, полезную нагрузку и идентификатор платежа, кредит один раз, а затем подтверждаете с помощьюSendMessage.
Храните факты о деньгах только в двух местах: invoice_payload вы сгенерировали, и оплата успешна Telegram отправляет обратно.
Как превратить Stars в производственного бота или мини-приложение? botservice.biz - отличное место для начала.