1. Go to this page and download the library: Download thexerc/laravel-bale library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
// Pin a message (silently, no notification)
Bale::pinChatMessage($chatId, $messageId, disableNotification: true);
// Unpin a specific message
Bale::unpinChatMessage($chatId, $messageId);
// Unpin the most recently pinned message
Bale::unpinChatMessage($chatId);
// Unpin all pinned messages
Bale::unpinAllChatMessages($chatId);
Bale::setChatTitle($chatId, 'My Awesome Group');
Bale::setChatDescription($chatId, 'The official support group.');
// setChatPhoto :getChatMembersCount($chatId);
// Mute a user
Bale::restrictChatMember($chatId, $userId, [
'can_send_messages' => false,
'can_send_media_messages' => false,
]);
// Restore permissions
Bale::restrictChatMember($chatId, $userId, [
'can_send_messages' => true,
'can_send_media_messages' => true,
]);
// Temporary restriction (lifts after 1 hour)
Bale::restrictChatMember($chatId, $userId, ['can_send_messages' => false], untilDate: time() + 3600);
// Promote to admin with specific rights
Bale::promoteChatMember($chatId, $userId, [
'can_delete_messages' => true,
'can_pin_messages' => true,
'can_invite_users' => true,
]);
// Demote back to member
Bale::promoteChatMember($chatId, $userId, [
'can_delete_messages' => false,
'can_pin_messages' => false,
]);
use TheXERC\Bale\Facades\Bale;
public function boot(): void
{
Bale::onCommand('start', \App\Bale\Handlers\StartHandler::class);
Bale::onCommand('help', \App\Bale\Handlers\HelpHandler::class);
}
use TheXERC\Bale\Facades\Bale;
$message = Bale::sendMessage($chatId, 'Hello, world!');
// Markdown
Bale::sendMessage($chatId, '*Bold* and _italic_', [
'parse_mode' => 'Markdown',
]);
// HTML
Bale::sendMessage($chatId, '<b>Bold</b> and <i>italic</i>', [
'parse_mode' => 'HTML',
]);
use TheXERC\Bale\Facades\Bale;
use TheXERC\Bale\Keyboards\InlineKeyboard;
use TheXERC\Bale\Keyboards\InlineKeyboardButton;
use TheXERC\Bale\Objects\CopyTextButton;
$keyboard = (new InlineKeyboard())
->row(
new InlineKeyboardButton('✅ Confirm', callbackData: 'confirm'),
new InlineKeyboardButton('❌ Cancel', callbackData: 'cancel'),
)
->row(
new InlineKeyboardButton('🌐 Visit Website', url: 'https://example.com'),
)
->row(
// Opens a Mini App
new InlineKeyboardButton('🚀 Open App', webAppUrl: 'https://your-app.com/miniapp'),
)
->row(
// Copy text to clipboard when tapped
new InlineKeyboardButton('📋 Copy Code', copyText: new CopyTextButton('DISCOUNT50')),
);
Bale::sendMessage($chatId, 'Choose an option:', [
'reply_markup' => $keyboard,
]);
use TheXERC\Bale\Keyboards\ReplyKeyboard;
use TheXERC\Bale\Keyboards\ReplyKeyboardButton;
$keyboard = (new ReplyKeyboard())
->row('📦 My Orders', '🔍 Search')
->row(
new ReplyKeyboardButton('📞 Share Contact', requestContact: true),
new ReplyKeyboardButton('📍 Share Location', requestLocation: true),
)
->row(
// Open a Mini App directly from a reply keyboard button
new ReplyKeyboardButton('🚀 Open App', webAppUrl: 'https://your-app.com/miniapp'),
)
->resize()
->oneTime();
Bale::sendMessage($chatId, 'What would you like to do?', [
'reply_markup' => $keyboard,
]);
use TheXERC\Bale\Keyboards\ReplyKeyboard;
Bale::sendMessage($chatId, 'Keyboard removed.', [
'reply_markup' => ReplyKeyboard::remove(),
]);
// By file_id (already on Bale servers — recommended)
Bale::sendPhoto($chatId, 'AgACAgIAAxkBAAI...', ['caption' => 'A nice photo']);
// By local file path (uploaded via multipart)
Bale::sendPhoto($chatId, '/path/to/photo.jpg', ['caption' => 'Uploaded photo']);
// By URL
Bale::sendPhoto($chatId, 'https://example.com/photo.jpg');
// Get a File object containing the file_path
$file = Bale::getFile($fileId);
// Build the download URL
$url = Bale::getFileUrl($file->filePath);
// Download the file to a local path
$localPath = Bale::downloadFile($file, storage_path('app/downloads/myfile.pdf'));
use TheXERC\Bale\Facades\Bale;
public function boot(): void
{
// Class-based handler
Bale::onCommand('start', \App\Bale\Handlers\StartHandler::class);
// Closure handler
Bale::onCommand('ping', function (Update $update, BotApi $bot) {
$bot->reply($update, 'Pong! 🏓');
});
}
namespace App\Bale\Handlers;
use TheXERC\Bale\BotApi;
use TheXERC\Bale\Objects\Update;
use TheXERC\Bale\Router\Handler;
class StartHandler extends Handler
{
public function handle(Update $update, BotApi $bot): void
{
$from = $update->getFrom();
$bot->sendMessage(
$update->getChatId(),
"👋 Hello {$from->getFullName()}!\n\nWelcome to the bot. Use /help to see commands.",
);
}
}
class StartHandler extends Handler
{
public function __construct(private UserRepository $users) {}
public function handle(Update $update, BotApi $bot): void
{
$from = $update->getFrom();
$this->users->firstOrCreate(['bale_id' => $from->id], [
'name' => $from->getFullName(),
]);
$bot->reply($update, 'Welcome back!');
}
}
// Match any message (no pattern)
Bale::onMessage(function (Update $update, BotApi $bot) {
$bot->reply($update, 'You said: '.$update->message->text);
});
// Match only messages containing a phone number
Bale::onMessage(\App\Bale\Handlers\PhoneHandler::class, '/\+?\d{10,}/');
// Handle all callback queries
Bale::onCallbackQuery(function (Update $update, BotApi $bot) {
$data = $update->callbackQuery->data;
$bot->answerCallbackQuery($update->callbackQuery->id, "You clicked: {$data}");
});
// Filter by data prefix
Bale::onCallbackQuery(\App\Bale\Handlers\OrderHandler::class, 'order:');
// Handles queries like "order:42", "order:cancel:5", etc.
public function handle(Update $update, BotApi $bot): void
{
$query = $update->callbackQuery;
$chatId = $query->message->chat->id;
$msgId = $query->message->messageId;
// Remove the inline keyboard after the click
$bot->editMessageText($chatId, $msgId, 'Order confirmed ✅');
$bot->answerCallbackQuery($query->id);
}
use TheXERC\Bale\Events\UpdateReceived;
use TheXERC\Bale\Events\MessageReceived;
use TheXERC\Bale\Events\EditedMessageReceived;
use TheXERC\Bale\Events\CommandReceived;
use TheXERC\Bale\Events\CallbackQueryReceived;
use TheXERC\Bale\Events\PreCheckoutQueryReceived;
use TheXERC\Bale\Events\PaymentReceived;
protected $listen = [
UpdateReceived::class => [App\Listeners\LogAllUpdates::class],
MessageReceived::class => [App\Listeners\HandleMessage::class],
EditedMessageReceived::class => [App\Listeners\HandleEditedMessage::class],
CommandReceived::class => [App\Listeners\HandleCommand::class],
CallbackQueryReceived::class => [App\Listeners\HandleCallback::class],
PreCheckoutQueryReceived::class => [App\Listeners\ApproveCheckout::class],
PaymentReceived::class => [App\Listeners\FulfillOrder::class],
];
namespace App\Listeners;
use TheXERC\Bale\Events\PaymentReceived;
class FulfillOrder
{
public function handle(PaymentReceived $event): void
{
$payment = $event->payment; // SuccessfulPayment object
$chatId = $event->update->getChatId();
// $payment->invoicePayload holds whatever you put in sendInvoice()
Order::where('payload', $payment->invoicePayload)->fulfill();
$event->bot->sendMessage($chatId, "✅ Payment received! Thank you.");
}
}
use TheXERC\Bale\Facades\Bale;
use TheXERC\Bale\Payments\LabeledPrice;
// 1. Send an invoice
Bale::sendInvoice(
chatId: $chatId,
title: 'Premium Subscription',
description: '30 days of access to all features.',
payload: 'sub_' . $userId,
providerToken: env('BALE_PAYMENT_TOKEN'),
prices: [
new LabeledPrice('Subscription', 50_000),
],
);
// 2. Handle pre_checkout_query — MUST answer within 10 seconds
Bale::onPreCheckoutQuery(function ($update, $bot) {
$query = $update->preCheckoutQuery;
// Validate stock, eligibility, etc.
$ok = true;
$bot->answerPreCheckoutQuery($query->id, ok: $ok);
// Or reject with a reason:
// $bot->answerPreCheckoutQuery($query->id, ok: false, errorMessage: 'Item out of stock');
});
// 3. Fulfil the order after successful payment (via PaymentReceived event — see above)
use TheXERC\Bale\Events\PreCheckoutQueryReceived;
class ApproveCheckout
{
public function handle(PreCheckoutQueryReceived $event): void
{
$event->bot->answerPreCheckoutQuery($event->preCheckoutQuery->id, ok: true);
}
}
Bale::onMessage(function (Update $update, BotApi $bot) {
$webAppData = $update->message->webAppData;
if ($webAppData) {
// $webAppData->data — raw string sent by the Mini App
// $webAppData->buttonText — label of the keyboard button that opened the app
// $webAppData->json() — decoded array if the data was JSON
$payload = $webAppData->json();
$bot->sendMessage($update->getChatId(), "Received order #{$payload['order_id']}");
}
});
use TheXERC\Bale\MiniApp\MiniAppHelper;
public function store(Request $request): JsonResponse
{
$initData = $request->header('X-Bale-Init-Data');
$token = config('bale.bots.default.token');
$result = MiniAppHelper::validate($initData, $token, ttl: 3600);
if (! $result['valid']) {
return response()->json(['error' => $result['error']], 401);
}
$user = $result['user']; // ['id' => ..., 'first_name' => ..., ...]
// Proceed with the authenticated user
}
// app/Http/Middleware/ValidateBaleUser.php
use TheXERC\Bale\MiniApp\MiniAppHelper;
class ValidateBaleUser
{
public function handle(Request $request, Closure $next): Response
{
$initData = $request->header('X-Bale-Init-Data');
$result = MiniAppHelper::validate($initData, config('bale.bots.default.token'));
if (! $result['valid']) {
return response()->json(['error' => 'Unauthorized'], 401);
}
// Attach the user to the request for downstream use
$request->attributes->set('bale_user', $result['user']);
return $next($request);
}
}
use TheXERC\Bale\Facades\Bale;
// Default bot
Bale::sendMessage($chatId, 'Hello from the default bot');
// Named bot
Bale::bot('support')->sendMessage($chatId, 'Hello from the support bot');
// Register handlers per bot
Bale::bot('shop')->onCommand('start', \App\Bale\Shop\StartHandler::class);
Bale::bot('shop')->onCommand('catalog', \App\Bale\Shop\CatalogHandler::class);
use TheXERC\Bale\Exceptions\BaleApiException;
try {
Bale::sendMessage($chatId, $text);
} catch (BaleApiException $e) {
if ($e->isRateLimited()) {
// $e->retryAfter — seconds to wait before retrying
sleep($e->retryAfter);
Bale::sendMessage($chatId, $text); // retry
} elseif ($e->migrateToChatId !== null) {
// Group was upgraded to supergroup — update stored chat ID
$newChatId = $e->migrateToChatId;
} else {
throw $e;
}
}