- Create AnsibleDeployStage using framework's Process module for secure command execution - Integrate AnsibleDeployStage into DeploymentPipelineCommands for production deployments - Add force_deploy flag support in Ansible playbook to override stale locks - Use PHP deployment module as orchestrator (php console.php deploy:production) - Fix ErrorAggregationInitializer to use Environment class instead of $_ENV superglobal Architecture: - BuildStage → AnsibleDeployStage → HealthCheckStage for production - Process module provides timeout, error handling, and output capture - Ansible playbook supports rollback via rollback-git-based.yml - Zero-downtime deployments with health checks
12 KiB
WhatsApp Notification Channel
Dokumentation für den WhatsApp Business API Notification Channel im Custom PHP Framework.
Übersicht
Der WhatsApp Channel ermöglicht das Versenden von Notifications über die WhatsApp Business API. Es werden sowohl Textnachrichten als auch Template-basierte Nachrichten unterstützt.
Features
✅ Text Messages: Einfache Textnachrichten mit Markdown-Formatierung ✅ Template Messages: WhatsApp-approved Message Templates mit Parametern ✅ Action Buttons: Support für Action URLs und Labels ✅ Type Safety: Framework-konforme Value Objects für alle Identifier ✅ HttpClient Integration: Nutzung des Framework's HttpClient Moduls ✅ Error Handling: Umfassende Exception-Behandlung mit WhatsAppApiException
Architektur
WhatsAppChannel (NotificationChannelInterface)
↓
WhatsAppClient (API Communication)
↓
HttpClient (Framework's HTTP Module)
↓
WhatsApp Business API
Installation & Setup
1. WhatsApp Business Account einrichten
- Erstelle einen WhatsApp Business Account bei Facebook
- Registriere deine Business Phone Number
- Generiere einen Access Token
- Notiere deine Phone Number ID und Business Account ID
URLs:
- WhatsApp Business Dashboard: https://business.facebook.com/settings/whatsapp-business-accounts
- Meta for Developers: https://developers.facebook.com/
2. Konfiguration
Die Konfiguration erfolgt aktuell hardcoded in WhatsAppConfig::createDefault():
use App\Framework\Notification\Channels\WhatsApp\WhatsAppConfig;
$config = WhatsAppConfig::createDefault();
// Oder manuell:
$config = new WhatsAppConfig(
accessToken: 'YOUR_ACCESS_TOKEN',
phoneNumberId: 'YOUR_PHONE_NUMBER_ID',
businessAccountId: WhatsAppBusinessAccountId::fromString('YOUR_BUSINESS_ACCOUNT_ID'),
apiVersion: 'v18.0'
);
Verwendung
Basic Text Message
use App\Framework\Core\ValueObjects\PhoneNumber;
use App\Framework\Notification\Notification;
use App\Framework\Notification\ValueObjects\NotificationChannel;
use App\Framework\Notification\ValueObjects\SystemNotificationType;
// Create notification
$notification = Notification::create(
recipientId: 'user_123',
type: SystemNotificationType::SYSTEM_ALERT(),
title: 'Important Update',
body: 'Your order has been shipped!',
NotificationChannel::WHATSAPP
);
// Send via dispatcher
$result = $notificationDispatcher->send($notification);
if ($result->isSuccessful()) {
echo "WhatsApp message sent: {$result->getMetadata()['message_id']}";
}
Template Message
WhatsApp erfordert pre-approved Templates für Marketing und Notifications.
$notification = Notification::create(
recipientId: 'user_123',
type: SystemNotificationType::ORDER_CONFIRMATION(),
title: 'Order Confirmation',
body: 'Template will be used',
NotificationChannel::WHATSAPP
)->withData([
'whatsapp_template_id' => 'order_confirmation',
'whatsapp_language' => 'en_US',
'whatsapp_template_params' => [
'John Doe', // Customer name
'ORD-12345', // Order number
'€99.99' // Total amount
]
]);
$result = $notificationDispatcher->send($notification);
With Action Button
$notification = Notification::create(
recipientId: 'user_123',
type: SystemNotificationType::PAYMENT_REMINDER(),
title: 'Payment Due',
body: 'Your invoice is ready for payment.',
NotificationChannel::WHATSAPP
)->withAction(
url: 'https://example.com/invoices/123',
label: 'View Invoice'
);
// Message will include: "👉 View Invoice: https://example.com/invoices/123"
Phone Number Resolver
Implementiere UserPhoneNumberResolver für deine Anwendung:
use App\Framework\Core\ValueObjects\PhoneNumber;
use App\Framework\Notification\Channels\WhatsApp\UserPhoneNumberResolver;
final readonly class DatabaseUserPhoneNumberResolver implements UserPhoneNumberResolver
{
public function __construct(
private UserRepository $userRepository
) {}
public function resolvePhoneNumber(string $userId): ?PhoneNumber
{
$user = $this->userRepository->find($userId);
if ($user === null || $user->phoneNumber === null) {
return null;
}
try {
return PhoneNumber::fromString($user->phoneNumber);
} catch (\InvalidArgumentException $e) {
// Invalid phone number format
return null;
}
}
}
Value Objects
PhoneNumber
use App\Framework\Core\ValueObjects\PhoneNumber;
// E.164 format required: +[country code][number]
$phone = PhoneNumber::fromString('+4917612345678');
// Or from parts
$phone = PhoneNumber::fromInternational('49', '17612345678');
// Methods
$phone->toString(); // +4917612345678
$phone->toDisplayFormat(); // +49 176 126 456 78
$phone->getCountryCode(); // 49
$phone->getSubscriberNumber(); // 17612345678
WhatsAppTemplateId
use App\Framework\Notification\Channels\WhatsApp\ValueObjects\WhatsAppTemplateId;
// Template names must be lowercase alphanumeric with underscores
$templateId = WhatsAppTemplateId::fromString('order_confirmation');
$templateId = WhatsAppTemplateId::fromString('hello_world');
// ❌ Invalid
$templateId = WhatsAppTemplateId::fromString('OrderConfirmation'); // Uppercase not allowed
$templateId = WhatsAppTemplateId::fromString('order-confirmation'); // Hyphen not allowed
WhatsAppBusinessAccountId
use App\Framework\Notification\Channels\WhatsApp\ValueObjects\WhatsAppBusinessAccountId;
$accountId = WhatsAppBusinessAccountId::fromString('123456789012345');
WhatsAppMessageId
use App\Framework\Notification\Channels\WhatsApp\ValueObjects\WhatsAppMessageId;
// Returned from API after sending
$messageId = WhatsAppMessageId::fromString('wamid.HBgNNDkxNzYxMjM0NTY3OBUCABIYFjNFQjBDNzE4RjAzMEE1NzQxODZEMDIA');
WhatsApp Templates
Template Erstellen
- Gehe zu WhatsApp Business Manager
- Navigiere zu "Message Templates"
- Erstelle ein neues Template
- Warte auf Approval (kann 24-48h dauern)
Template Beispiel
Template Name: order_confirmation
Language: English (US)
Category: Transactional
Body:
Hello {{1}},
Your order {{2}} has been confirmed!
Total amount: {{3}}
Thank you for your purchase.
Usage:
$notification->withData([
'whatsapp_template_id' => 'order_confirmation',
'whatsapp_language' => 'en_US',
'whatsapp_template_params' => [
'John Doe', // {{1}}
'ORD-12345', // {{2}}
'€99.99' // {{3}}
]
]);
Testing
Manual Test Script
docker exec php php tests/debug/test-whatsapp-notification.php
Wichtig: Ersetze die Test-Telefonnummer in der Datei mit deiner eigenen WhatsApp-Nummer!
Unit Test
use App\Framework\Notification\Channels\WhatsAppChannel;
use App\Framework\Notification\Notification;
it('sends WhatsApp notification successfully', function () {
$mockClient = Mockery::mock(WhatsAppClient::class);
$mockResolver = Mockery::mock(UserPhoneNumberResolver::class);
$mockResolver->shouldReceive('resolvePhoneNumber')
->with('user_123')
->andReturn(PhoneNumber::fromString('+4917612345678'));
$mockClient->shouldReceive('sendTextMessage')
->once()
->andReturn(new WhatsAppResponse(
success: true,
messageId: WhatsAppMessageId::fromString('wamid_test_123')
));
$channel = new WhatsAppChannel($mockClient, $mockResolver);
$notification = Notification::create(
recipientId: 'user_123',
type: SystemNotificationType::SYSTEM_ALERT(),
title: 'Test',
body: 'Test message',
NotificationChannel::WHATSAPP
);
$result = $channel->send($notification);
expect($result->isSuccessful())->toBeTrue();
});
Error Handling
WhatsAppApiException
use App\Framework\Notification\Channels\WhatsApp\WhatsAppApiException;
try {
$response = $whatsappClient->sendTextMessage($phoneNumber, $message);
} catch (WhatsAppApiException $e) {
// API returned an error
$httpCode = $e->getHttpStatusCode();
$message = $e->getMessage();
// Log or handle error
$logger->error('WhatsApp API error', [
'http_code' => $httpCode,
'message' => $message
]);
}
Common Errors
| Error Code | Beschreibung | Lösung |
|---|---|---|
| 100 | Invalid parameter | Prüfe Parameter (phone number format, template ID) |
| 131009 | Parameter value not valid | Template parameters stimmen nicht mit Template überein |
| 131026 | Message undeliverable | Empfänger hat WhatsApp nicht oder blockiert Business Account |
| 131047 | Re-engagement message | User muss zuerst Business Account kontaktieren |
| 190 | Access token expired | Generiere neuen Access Token |
Best Practices
1. Phone Number Validation
// ✅ Validate before using
try {
$phone = PhoneNumber::fromString($userInput);
} catch (\InvalidArgumentException $e) {
// Handle invalid phone number
return 'Invalid phone number format';
}
// ❌ Don't assume format
$phone = PhoneNumber::fromString($_POST['phone']); // Can throw exception
2. Template Usage
// ✅ Use templates for marketing/promotional content
$notification->withData([
'whatsapp_template_id' => 'weekly_newsletter',
'whatsapp_language' => 'en_US'
]);
// ✅ Use text messages for immediate transactional updates
$notification = Notification::create(
recipientId: 'user_123',
type: SystemNotificationType::SYSTEM_ALERT(),
title: 'Server Alert',
body: 'Critical: Database connection lost!',
NotificationChannel::WHATSAPP
);
3. Rate Limiting
WhatsApp hat Rate Limits pro Business Account:
- Tier 1 (default): 1,000 unique contacts/24h
- Tier 2: 10,000 unique contacts/24h
- Tier 3: 100,000 unique contacts/24h
// Implement rate limiting
if ($this->rateLimiter->tooManyAttempts("whatsapp:{$userId}", 5, 3600)) {
throw new RateLimitException('Too many WhatsApp messages sent');
}
4. Opt-In Requirement
WhatsApp erfordert explicit Opt-In von Usern:
// Check user consent before sending
if (!$user->hasWhatsAppOptIn()) {
return ChannelResult::failure(
channel: NotificationChannel::WHATSAPP,
errorMessage: 'User has not opted in to WhatsApp notifications'
);
}
Troubleshooting
Message nicht zugestellt
Checklist:
- Phone Number ist in E.164 Format (
+4917612345678) - Empfänger hat WhatsApp installiert
- Empfänger hat Business Account nicht blockiert
- Access Token ist gültig
- Template ist approved (für Template Messages)
- Rate Limits nicht überschritten
Template Errors
Problem: "Parameter value not valid" Lösung: Anzahl der Parameter muss exakt mit Template übereinstimmen
// Template hat 3 placeholders: {{1}}, {{2}}, {{3}}
// ✅ Correct
'whatsapp_template_params' => ['Param1', 'Param2', 'Param3']
// ❌ Wrong - zu wenige Parameter
'whatsapp_template_params' => ['Param1', 'Param2']
Access Token Expired
Problem: Error 190 - Access token has expired Lösung: Generiere neuen Access Token im Facebook Business Manager
Weiterführende Ressourcen
- WhatsApp Business API Docs: https://developers.facebook.com/docs/whatsapp/cloud-api
- Message Templates: https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates
- Error Codes: https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes
- E.164 Format: https://en.wikipedia.org/wiki/E.164
Framework Integration
Der WhatsApp Channel folgt allen Framework-Patterns:
✅ Readonly Classes: Alle VOs und Configs sind final readonly
✅ Value Objects: Keine Primitive Obsession (PhoneNumber, TemplateId, etc.)
✅ No Inheritance: Composition über Inheritance
✅ Type Safety: Strikte Typisierung für alle Parameter
✅ Framework Compliance: Integration mit HttpClient, Notification System
✅ Explicit Dependencies: Constructor Injection, keine Service Locators