Files
Joshua Fernandes a97e579635 feat(plugins): add sale document view hooks, webhook CSRF exemption, and WhatsApp plugin
Add three missing plugin hook points for sale documents so plugins can
inject buttons into invoice, quote, and work order views alongside the
existing receipt hook. All four pass ['saleId' => $sale_id_num] and
follow the same naming pattern (view:sales_{type}_buttons).

Exempt plugins/*/webhook from CSRF filtering to support server-to-server
provider callbacks. Also convert the CSRF except list from a string to an
array — the previous 'login|migrate' string produced a single unanchored
pattern, making login/anything CSRF-exempt. Separate array entries anchor
each one individually. Plugin webhook handlers are responsible for their
own authentication.

Add WhatsApp Business Cloud API plugin (app/Plugins/WhatsAppPlugin/):
- Free-form messaging page registered as the 'whatsapp' office module
  with its own permission, plus per-customer modal and thread view
- "Send via WhatsApp" button on all four sale document types via the new
  hooks; renders only when the customer has a phone number
- PDF delivery using the same sales/{type}_email view core uses for email
- Inbound webhook at plugins/whatsapp/webhook authenticated by
  X-Hub-Signature-256 HMAC; fails closed on missing/bad signature;
  always returns 200 to suppress Meta retries
- Out-of-order status callbacks cannot downgrade sent → delivered → read
- Conversation log table via plugin migration; dropped on uninstall with
  version reset so re-install recreates it cleanly
- Access token and app secret encrypted at rest in plugin_config; no
  writes to app_config or initial_schema.sql
- utf8mb4_unicode_520_ci collation throughout (MySQL and MariaDB compat)
- Language file stubs for all existing locales; English strings complete
- README covering credentials, install, webhook setup, and uninstall
2026-09-07 12:32:37 +04:00

69 lines
5.0 KiB
PHP

<?php
return [
'api_url' => 'API Base URL',
'api_version' => 'API Version',
'app_secret' => 'App Secret',
'app_secret_help' => 'Required to receive replies: inbound payloads are rejected unless their signature matches this secret.',
'business_id' => 'WhatsApp Business Account ID',
'clear_secret' => 'Clear the stored value',
'configuration' => 'WhatsApp Configuration',
'conversation' => 'Conversation',
'default_country_code' => 'Default Country Code',
'default_country_code_placeholder' => 'e.g. 1',
'description' => 'Send WhatsApp messages and sale documents to customers through the WhatsApp Business Cloud API, and view the replies.',
'docs_link' => 'How to get these credentials — read the WhatsApp Business Cloud API documentation',
'document_failed' => 'The document could not be generated.',
'first_name' => 'First name',
'invoice_sent' => 'Invoice sent via WhatsApp to',
'invoice_unsent' => 'Invoice failed to be sent via WhatsApp to',
'last_activity' => 'Last activity',
'last_name' => 'Last name',
'media_upload_failed' => 'The document could not be uploaded to WhatsApp.',
'message' => 'Message',
'message_placeholder' => 'Your message here...',
'message_required' => 'Message required',
'messages' => 'Messages',
'no_conversations' => 'No conversations yet.',
'no_messages' => 'No messages yet.',
'no_phone' => 'This customer does not have a valid phone number.',
'not_configured' => 'WhatsApp is not configured or is disabled.',
'not_configured_notice' => 'WhatsApp is not configured. Set your credentials in the plugin configuration before sending messages.',
'phone' => 'Phone number',
'phone_help' => 'Include the country code (digits only), e.g. 14155552671',
'phone_id' => 'Phone Number ID',
'phone_id_required' => 'Phone Number ID is a required field',
'phone_number_required' => 'Phone number required',
'phone_placeholder' => 'Phone number with country code...',
'quote_sent' => 'Quote sent via WhatsApp to',
'quote_unsent' => 'Quote failed to be sent via WhatsApp to',
'receipt_sent' => 'Receipt sent via WhatsApp to',
'receipt_unsent' => 'Receipt failed to be sent via WhatsApp to',
'recent_conversations' => 'Recent conversations',
'saved_message' => 'Saved Text Message',
'saved_message_placeholder' => 'Default message pre-filled on the WhatsApp send page. Leave blank for none.',
'secret_stored' => 'A value is stored — leave blank to keep it',
'secret_unchanged_help' => 'The stored value is never displayed. Leave the field blank to keep it, type a new one to replace it, or tick the box to remove it.',
'send_failed' => 'The WhatsApp API rejected the message.',
'send_whatsapp' => 'Send via WhatsApp',
'status_delivered' => 'delivered',
'status_failed' => 'failed',
'status_read' => 'read',
'status_received' => 'received',
'status_sent' => 'sent',
'successfully_sent' => 'WhatsApp message successfully sent to:',
'token' => 'Access Token',
'token_required' => 'Access Token is a required field',
'unsuccessfully_sent' => 'WhatsApp message could not be sent to:',
'verify_token' => 'Verify Token',
'view' => 'View',
'webhook_heading' => 'Webhook (inbound replies)',
'webhook_url' => 'Callback URL',
'webhook_url_help' => 'Register this URL in the Meta app dashboard. It must be publicly reachable over HTTPS to receive replies.',
'whatsapp' => 'WhatsApp',
'whatsapp_send' => 'Send WhatsApp',
'window_notice' => 'Note: free-form messages are only delivered within 24 hours of the customer\'s last message. Outside that window Meta requires an approved message template.',
'work_order_sent' => 'Work Order sent via WhatsApp to',
'work_order_unsent' => 'Work Order failed to be sent via WhatsApp to',
];