3neti / messaging-bot
Multi-channel messaging bot for PayCode voucher operations (Telegram, WhatsApp, Viber)
Requires
- php: ^8.2
- 3neti/voucher: ^0.9
- guzzlehttp/guzzle: ^7.0
- illuminate/cache: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/routing: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- spatie/laravel-data: ^4.0
Requires (Dev)
- orchestra/testbench: ^10.0
- pestphp/pest: ^3.0
README
Multi-channel messaging bot for PayCode voucher operations. Currently supports Telegram, with WhatsApp and Viber planned for future phases.
Features
- Multi-step conversation flows - Stateful conversations for complex operations
- Platform-agnostic core - Normalized DTOs work across all messaging platforms
- Pluggable drivers - Easy to add new messaging platforms
- Admin authorization - Restrict commands to configured admin chat IDs
- Laravel Cache integration - Conversation state persists across messages
Installation
The package is included in the mono-repo. Add it to your host app's composer.json:
{
"repositories": [
{
"type": "path",
"url": "./packages/messaging-bot"
}
],
"require": {
"lbhurtado/messaging-bot": "@dev"
}
}
Then run:
composer update lbhurtado/messaging-bot
Configuration
Publish the config file:
php artisan vendor:publish --tag=messaging-bot-config
Environment Variables
# Telegram Bot TELEGRAM_BOT_TOKEN=your_bot_token_here TELEGRAM_WEBHOOK_SECRET=your_secret_here TELEGRAM_ADMIN_CHAT_IDS=123456789,987654321 # General MESSAGING_BOT_CONVERSATION_TTL=1800
Setting Up Telegram
1. Create a Bot
- Open Telegram and search for
@BotFather - Send
/newbotand follow the prompts - Copy the bot token to
TELEGRAM_BOT_TOKEN
2. Set Webhook
Set your webhook URL (replace with your domain):
curl -X POST "https://api.telegram.org/bot<TOKEN>/setWebhook" \ -H "Content-Type: application/json" \ -d '{"url": "https://yourdomain.com/messaging/telegram/webhook", "secret_token": "your_secret"}'
Or use the driver directly:
use LBHurtado\MessagingBot\Drivers\Telegram\TelegramDriver; $driver = app(TelegramDriver::class); $driver->setWebhook('https://yourdomain.com/messaging/telegram/webhook');
3. Local Development
For local development, use the test command to simulate messages:
# Simulate a message php artisan test:messaging "/redeem" # Continue the conversation php artisan test:messaging "VOUCHER-CODE" # Simulate contact sharing (phone number) php artisan test:messaging "" --contact # Test deep link (as if user clicked t.me/bot?start=redeem_CODE) php artisan test:messaging "/start redeem_ABCD" # Use different chat ID (for separate conversation state) php artisan test:messaging "/redeem" --chat-id=99999 # Specify mobile number for contact sharing php artisan test:messaging "" --contact --mobile=09181234567
Alternatively, use long polling for real Telegram interaction:
php artisan messaging:poll
Available Commands
Public Commands
| Command | Description |
|---|---|
/start |
Welcome message |
/help |
Show available commands |
/balance |
Check wallet balance (requires linked account) |
/redeem |
Start voucher redemption flow |
/cancel |
Cancel current operation |
Admin Commands
| Command | Description |
|---|---|
/generate |
Generate multiple vouchers |
/disburse |
Quick single-voucher creation |
Deep Links
Telegram deep links allow users to start redemption with a single click:
https://t.me/your_bot?start=redeem_VOUCHER-CODE
https://t.me/your_bot?start=disburse_VOUCHER-CODE
Format: start=action_param (underscore separator)
Examples:
https://t.me/xchange_paycode_bot?start=redeem_3LGB-GX8S
https://t.me/xchange_paycode_bot?start=disburse_ABCD-1234
When a user clicks a deep link:
- Telegram opens and starts the bot
- Bot automatically validates the voucher code
- User sees amount and is prompted to share phone (or confirm if returning user)
Redemption UX Flow
First-Time User (3 steps)
Click: t.me/bot?start=redeem_ABCD
→ ✅ ₱100.00 found!
We need your mobile number to send the funds.
[📱 Share Phone Number]
*taps share*
→ 📋 You will receive:
₱100.00 → GCash:09173011987
[✅ Accept] [✏️ Change Account]
*taps accept*
→ 🎉 Done!
Returning User (2 steps)
Phone number is cached for 30 days after successful redemption:
Click: t.me/bot?start=redeem_EFGH
→ ✅ ₱100.00 found!
Send to GCash:09173011987?
[✅ Accept] [📱 Different Number]
*taps accept*
→ 🎉 Done!
Architecture
┌─────────────────────────────────────────────────────────┐
│ Drivers (Platform-Specific) │
│ TelegramDriver │ WhatsAppDriver │ ViberDriver │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ Core Engine │
│ MessagingKernel → IntentRouter → Handler/Flow │
│ ConversationStore (Laravel Cache) │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ Domain Actions │
│ GenerateVouchers │ RedeemViaSms │ ProcessRedemption │
└─────────────────────────────────────────────────────────┘
Adding a New Handler
Create a handler extending BaseMessagingHandler:
namespace LBHurtado\MessagingBot\Handlers; use LBHurtado\MessagingBot\Data\NormalizedResponse; use LBHurtado\MessagingBot\Data\NormalizedUpdate; class MyHandler extends BaseMessagingHandler { protected function process(NormalizedUpdate $update): NormalizedResponse { return NormalizedResponse::text('Hello from MyHandler!'); } // Optional: require authentication public function requiresAuth(): bool { return true; } }
Register in config:
// config/messaging-bot.php 'handlers' => [ 'my_intent' => \LBHurtado\MessagingBot\Handlers\MyHandler::class, ],
Adding a New Flow
Create a flow extending BaseFlow:
namespace LBHurtado\MessagingBot\Flows; class MyFlow extends BaseFlow { public function initialStep(): string { return 'step1'; } public function steps(): array { return ['step1', 'step2', 'finalize']; } protected function promptStep1(ConversationState $state): NormalizedResponse { return NormalizedResponse::text('Enter something:'); } protected function handleStep1(NormalizedUpdate $update, ConversationState $state, string $input): array { $newState = $state->with('value', $input)->advanceTo('step2'); return [ 'response' => $this->promptStep2($newState), 'state' => $newState, ]; } // ... more steps }
Webhook Endpoints
| Platform | Endpoint |
|---|---|
| Telegram | POST /messaging/telegram/webhook |
POST /messaging/whatsapp/webhook (planned) |
|
| Viber | POST /messaging/viber/webhook (planned) |
Testing
Unit Tests
# Run package tests cd packages/messaging-bot composer test # Or from root php artisan test packages/messaging-bot/tests
Manual Testing
# Test the full redemption flow php artisan cache:clear php artisan test:messaging "/start redeem_VOUCHER-CODE" php artisan test:messaging "" --contact php artisan test:messaging "accept" # Test returning user (with cached phone) php artisan tinker --execute="Cache::put('messaging:phone:12345', '+639173011987', now()->addDays(30));" php artisan test:messaging "/start redeem_ANOTHER-CODE" php artisan test:messaging "accept"
License
Proprietary - All rights reserved.