Принимайте платежи Telegram Stars на PHP: sendInvoice, pre_checkout_query и successful_payment проверки серверов

Что мы строим и что пропускаем

В этом руководстве показан минимальный путь PHP для принятия Telegram Stars через Bot API. Вы будете:

  1. Отправить счет sendInvoice в валюте. XTR.
  2. Рукоятка; pre_checkout_query и звоните answerPreCheckoutQuery in time
  3. Получить message.successful_payment и подтвердите идентификатор списания, сумму Stars и полезную нагрузку счета.
  4. Избегайте распространенных ошибок: отвечайте слишком поздно, относитесь к 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);

Проверьте все это:

  1. currency === 'XTR'
  2. invoice_payload соотносится с отложенным заказом
  3. Итоговая цена соответствует ожидаемым звездам
  4. 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 для XTRsendInvoice -
  • Обращение со Звездами как с наличными в банке.
  • Возврат ошибок на оплата успешна навсегда. Журнал, возврат 200, и охранник с уникальным telegram_payment_charge_id.

6. Сквозная форма

  1. Пользователь нажимает Купить. Вы answerCallbackQuery и звоните sendInvoice где currency=XTR.
  2. Telegram показывает лист Stars. При подтверждении отправляет pre_checkout_query; вы отвечаете только после повторной проверки заказа.
  3. На заряд, Telegram отправляет оплата успешна. Вы подтверждаете валюту, сумму, полезную нагрузку и идентификатор платежа, кредит один раз, а затем подтверждаете с помощью SendMessage.

Храните факты о деньгах только в двух местах: invoice_payload вы сгенерировали, и оплата успешна Telegram отправляет обратно.

Как превратить Stars в производственного бота или мини-приложение? botservice.biz - отличное место для начала.

Новые статьи — в Telegram

Разбираем, что автоматизировать в бизнесе и как это работает на практике. Без спама.