feat(Deployment): Integrate Ansible deployment via PHP deployment pipeline
- 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
This commit is contained in:
@@ -300,30 +300,58 @@ final readonly class UserCommands
|
||||
```html
|
||||
<!-- ✅ Framework Template Patterns -->
|
||||
<div class="user-card">
|
||||
<h2>{user.name}</h2>
|
||||
<p>{user.email}</p>
|
||||
<!-- Object property access -->
|
||||
<h2>{{ $user->name }}</h2>
|
||||
<p>{{ $user->email }}</p>
|
||||
|
||||
<!-- Conditional Rendering -->
|
||||
<if condition="user.isAdmin">
|
||||
<span class="badge">Admin</span>
|
||||
</if>
|
||||
<!-- Method calls -->
|
||||
<p>{{ $user->getFullName() }}</p>
|
||||
|
||||
<!-- Loop Rendering -->
|
||||
<for items="user.posts" as="post">
|
||||
<article>
|
||||
<h3>{post.title}</h3>
|
||||
<p>{post.excerpt}</p>
|
||||
</for>
|
||||
</for>
|
||||
<!-- Conditional Rendering - if attribute -->
|
||||
<span class="badge" if="{{ $user->isAdmin() }}">Admin</span>
|
||||
|
||||
<!-- Negation -->
|
||||
<p if="!{{ $user->isAdmin() }}">Regular User</p>
|
||||
|
||||
<!-- Loop Rendering - foreach attribute (PHP-style) -->
|
||||
<article foreach="$user->posts as $post">
|
||||
<h3>{{ $post->title }}</h3>
|
||||
<p>{{ $post->getExcerpt() }}</p>
|
||||
</article>
|
||||
|
||||
<!-- Loop with key-value pairs -->
|
||||
<div foreach="$items as $key => $value">
|
||||
<span>{{ $key }}: {{ $value }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Component Inclusion -->
|
||||
<include template="components/avatar" data="user.avatar" />
|
||||
<include template="components/avatar" data="{{ $user->avatar }}" />
|
||||
|
||||
<!-- Slot System -->
|
||||
<slot name="header">Default Header</slot>
|
||||
</div>
|
||||
```
|
||||
|
||||
**CRITICAL TEMPLATE ENGINE RULES**:
|
||||
1. **Placeholder Syntax**: ALWAYS `{{ $variable }}` with dollar sign
|
||||
2. **Object Access**:
|
||||
- Properties: `{{ $object->property }}`
|
||||
- Methods: `{{ $object->method() }}`
|
||||
- Arrays: `{{ $array['key'] }}` (still supported)
|
||||
3. **Conditional Rendering**: Use `if` attribute
|
||||
- Example: `<div if="{{ $hasData }}">content</div>`
|
||||
- Negation: `<div if="!{{ $hasData }}">no data</div>`
|
||||
4. **Loop Rendering**: Use `foreach` attribute (PHP-style)
|
||||
- Simple: `<div foreach="$items as $item">{{ $item->name }}</div>`
|
||||
- With key: `<tr foreach="$models as $index => $model">...</tr>`
|
||||
5. **NO custom tags for logic**: Only standard HTML tags with attributes
|
||||
|
||||
**PHP-Style Syntax Benefits**:
|
||||
- Native PHP developers immediately understand the syntax
|
||||
- Object properties and methods work naturally
|
||||
- `foreach` syntax identical to PHP
|
||||
- Supports key-value iteration out of the box
|
||||
|
||||
**Template Processors Integration**:
|
||||
```php
|
||||
// ✅ Custom Template Processor Pattern
|
||||
@@ -348,6 +376,17 @@ final readonly class DesignSystemProcessor
|
||||
}
|
||||
```
|
||||
|
||||
**Registered Template Processors**:
|
||||
- **PlaceholderReplacer**: Variable substitution with `{{ $var }}` syntax, object access `{{ $obj->prop }}`, method calls `{{ $obj->method() }}`
|
||||
- **ForeachAttributeProcessor**: Loop rendering via `foreach="$items as $item"` attribute
|
||||
- **IfAttributeProcessor**: Conditional rendering via `if="{{ $condition }}"` attribute
|
||||
- **ComponentProcessor**: Component inclusion & slot system
|
||||
- **LayoutTagProcessor**: Layout system integration
|
||||
- **MetaManipulator**: Meta tags & SEO management
|
||||
- **AssetInjector**: CSS/JS asset management
|
||||
- **CsrfTokenProcessor**: Security integration
|
||||
- **HoneypotProcessor**: Spam protection
|
||||
|
||||
**CSS Architecture (ITCSS) Expertise**:
|
||||
|
||||
**Layer Structure**:
|
||||
@@ -514,16 +553,14 @@ enum SpacingSize: string
|
||||
<!-- ✅ WCAG-compliant Templates -->
|
||||
<nav aria-label="Main navigation">
|
||||
<ul role="list">
|
||||
<for items="menuItems" as="item">
|
||||
<li>
|
||||
<a
|
||||
href="{item.url}"
|
||||
aria-current="{item.isActive ? 'page' : null}"
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
</for>
|
||||
<li foreach="$menuItems as $item">
|
||||
<a
|
||||
href="{{ $item['url'] }}"
|
||||
aria-current="{{ $item['isActive'] ? 'page' : null }}"
|
||||
>
|
||||
{{ $item['label'] }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -540,16 +577,14 @@ enum SpacingSize: string
|
||||
aria-labelledby="email-label"
|
||||
aria-describedby="email-hint email-error"
|
||||
aria-required="true"
|
||||
aria-invalid="{hasError ? 'true' : 'false'}"
|
||||
aria-invalid="{{ $hasError ? 'true' : 'false' }}"
|
||||
/>
|
||||
<span id="email-hint" class="form-hint">
|
||||
We'll never share your email
|
||||
</span>
|
||||
<if condition="hasError">
|
||||
<span id="email-error" role="alert" class="form-error">
|
||||
{errorMessage}
|
||||
</span>
|
||||
</if>
|
||||
<span id="email-error" role="alert" class="form-error" if="{{ $hasError }}">
|
||||
{{ $errorMessage }}
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
@@ -629,16 +664,21 @@ final readonly class DesignSystemRegistry
|
||||
- **Design Token Coverage**: 100% - keine Hard-coded Colors/Spacing
|
||||
|
||||
**Integration mit Template Processors**:
|
||||
- **PlaceholderReplacer**: Variable Substitution
|
||||
- **PlaceholderReplacer**: Variable Substitution mit `{{ $var }}` Syntax
|
||||
- **ComponentProcessor**: Component Inclusion & Slot System
|
||||
- **ForProcessor**: Loop Rendering
|
||||
- **IfProcessor**: Conditional Rendering
|
||||
- **ForAttributeProcessor**: Loop Rendering via `for-items` und `for-value` Attribute
|
||||
- **IfAttributeProcessor**: Conditional Rendering via `if` Attribut (+ `condition` deprecated fallback)
|
||||
- **LayoutTagProcessor**: Layout System
|
||||
- **MetaManipulator**: Meta Tags & SEO
|
||||
- **AssetInjector**: CSS/JS Asset Management
|
||||
- **CsrfTokenProcessor**: Security Integration
|
||||
- **HoneypotProcessor**: Spam Protection
|
||||
|
||||
**Deprecated Syntax (backwards compatible)**:
|
||||
- ❌ `<for items="..." as="...">` → ✅ Use `for-items` and `for-value` attributes
|
||||
- ❌ `<if condition="...">` → ✅ Use `if` attribute on element
|
||||
- ❌ `condition` attribute → ✅ Use `if` attribute (condition still supported)
|
||||
|
||||
**Performance Optimization**:
|
||||
```php
|
||||
// ✅ Critical CSS Extraction
|
||||
|
||||
438
docs/whatsapp-notification-channel.md
Normal file
438
docs/whatsapp-notification-channel.md
Normal file
@@ -0,0 +1,438 @@
|
||||
# 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
|
||||
|
||||
1. Erstelle einen WhatsApp Business Account bei Facebook
|
||||
2. Registriere deine Business Phone Number
|
||||
3. Generiere einen Access Token
|
||||
4. 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()`:
|
||||
|
||||
```php
|
||||
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
|
||||
|
||||
```php
|
||||
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.
|
||||
|
||||
```php
|
||||
$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
|
||||
|
||||
```php
|
||||
$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:
|
||||
|
||||
```php
|
||||
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
|
||||
|
||||
```php
|
||||
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
|
||||
|
||||
```php
|
||||
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
|
||||
|
||||
```php
|
||||
use App\Framework\Notification\Channels\WhatsApp\ValueObjects\WhatsAppBusinessAccountId;
|
||||
|
||||
$accountId = WhatsAppBusinessAccountId::fromString('123456789012345');
|
||||
```
|
||||
|
||||
### WhatsAppMessageId
|
||||
|
||||
```php
|
||||
use App\Framework\Notification\Channels\WhatsApp\ValueObjects\WhatsAppMessageId;
|
||||
|
||||
// Returned from API after sending
|
||||
$messageId = WhatsAppMessageId::fromString('wamid.HBgNNDkxNzYxMjM0NTY3OBUCABIYFjNFQjBDNzE4RjAzMEE1NzQxODZEMDIA');
|
||||
```
|
||||
|
||||
## WhatsApp Templates
|
||||
|
||||
### Template Erstellen
|
||||
|
||||
1. Gehe zu WhatsApp Business Manager
|
||||
2. Navigiere zu "Message Templates"
|
||||
3. Erstelle ein neues Template
|
||||
4. 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**:
|
||||
```php
|
||||
$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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```php
|
||||
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
|
||||
|
||||
```php
|
||||
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
|
||||
|
||||
```php
|
||||
// ✅ 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
|
||||
|
||||
```php
|
||||
// ✅ 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
|
||||
|
||||
```php
|
||||
// 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:
|
||||
|
||||
```php
|
||||
// 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
|
||||
|
||||
```php
|
||||
// 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
|
||||
Reference in New Issue
Block a user