mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-09-13 22:08:16 -04:00
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
232 lines
10 KiB
PHP
232 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* @var array $settings
|
|
* @var string $webhook_url
|
|
*/
|
|
|
|
$token_configured = ! empty($settings['token_configured']);
|
|
$app_secret_configured = ! empty($settings['app_secret_configured']);
|
|
?>
|
|
|
|
<?= form_open(site_url('plugins/saveConfig/whatsapp'), ['id' => 'config_form', 'enctype' => 'multipart/form-data', 'class' => 'form-horizontal']) ?>
|
|
<div id="config_wrapper">
|
|
<fieldset id="config_info">
|
|
|
|
<div id="required_fields_message"><?= lang('Common.fields_required_message') ?></div>
|
|
<div id="plugins_header"><?= lang('WhatsAppPlugin.configuration') ?></div>
|
|
<ul id="error_message_box" class="error_message_box"></ul>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('WhatsAppPlugin.phone_id'), 'phone_id', ['class' => 'control-label col-xs-3 required']) ?>
|
|
<div class="col-xs-8">
|
|
<div class="input-group">
|
|
<span class="input-group-addon input-sm"><span class="glyphicon glyphicon-phone"></span></span>
|
|
<?= form_input([
|
|
'name' => 'phone_id',
|
|
'id' => 'phone_id',
|
|
'class' => 'form-control input-sm required',
|
|
'value' => esc($settings['phone_id'] ?? ''),
|
|
]) ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('WhatsAppPlugin.business_id'), 'business_id', ['class' => 'control-label col-xs-3']) ?>
|
|
<div class="col-xs-8">
|
|
<?= form_input([
|
|
'name' => 'business_id',
|
|
'id' => 'business_id',
|
|
'class' => 'form-control input-sm',
|
|
'value' => esc($settings['business_id'] ?? ''),
|
|
]) ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('WhatsAppPlugin.token'), 'token', ['class' => 'control-label col-xs-3 required']) ?>
|
|
<div class="col-xs-8">
|
|
<div class="input-group">
|
|
<span class="input-group-addon input-sm"><span class="glyphicon glyphicon-lock"></span></span>
|
|
<?= form_password([
|
|
'name' => 'token',
|
|
'id' => 'token',
|
|
'class' => 'form-control input-sm' . ($token_configured ? '' : ' required'),
|
|
'value' => '',
|
|
'autocomplete' => 'new-password',
|
|
'placeholder' => $token_configured ? lang('WhatsAppPlugin.secret_stored') : '',
|
|
]) ?>
|
|
</div>
|
|
<?php if ($token_configured): ?>
|
|
<div class="checkbox">
|
|
<label>
|
|
<input type="checkbox" name="clear_token" value="1"> <?= lang('WhatsAppPlugin.clear_secret') ?>
|
|
</label>
|
|
</div>
|
|
<span class="help-block"><?= lang('WhatsAppPlugin.secret_unchanged_help') ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('WhatsAppPlugin.api_url'), 'api_url', ['class' => 'control-label col-xs-3']) ?>
|
|
<div class="col-xs-8">
|
|
<?= form_input([
|
|
'name' => 'api_url',
|
|
'id' => 'api_url',
|
|
'class' => 'form-control input-sm',
|
|
'value' => esc($settings['api_url'] ?? 'https://graph.facebook.com'),
|
|
]) ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('WhatsAppPlugin.api_version'), 'api_version', ['class' => 'control-label col-xs-3']) ?>
|
|
<div class="col-xs-4">
|
|
<?= form_input([
|
|
'name' => 'api_version',
|
|
'id' => 'api_version',
|
|
'class' => 'form-control input-sm',
|
|
'value' => esc($settings['api_version'] ?? 'v21.0'),
|
|
]) ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('WhatsAppPlugin.default_country_code'), 'default_country_code', ['class' => 'control-label col-xs-3']) ?>
|
|
<div class="col-xs-4">
|
|
<?= form_input([
|
|
'name' => 'default_country_code',
|
|
'id' => 'default_country_code',
|
|
'class' => 'form-control input-sm',
|
|
'value' => esc($settings['default_country_code'] ?? ''),
|
|
'placeholder' => lang('WhatsAppPlugin.default_country_code_placeholder'),
|
|
]) ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('WhatsAppPlugin.saved_message'), 'saved_message', ['class' => 'control-label col-xs-3']) ?>
|
|
<div class="col-xs-8">
|
|
<?= form_textarea([
|
|
'name' => 'saved_message',
|
|
'id' => 'saved_message',
|
|
'class' => 'form-control input-sm',
|
|
'value' => $settings['saved_message'] ?? '',
|
|
'placeholder' => lang('WhatsAppPlugin.saved_message_placeholder'),
|
|
]) ?>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
<div id="plugins_header"><?= lang('WhatsAppPlugin.webhook_heading') ?></div>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('WhatsAppPlugin.webhook_url'), 'webhook_url', ['class' => 'control-label col-xs-3']) ?>
|
|
<div class="col-xs-8">
|
|
<?= form_input([
|
|
'name' => 'webhook_url_display',
|
|
'id' => 'webhook_url_display',
|
|
'class' => 'form-control input-sm',
|
|
'value' => esc($webhook_url ?? ''),
|
|
'readonly' => 'true',
|
|
]) ?>
|
|
<span class="help-block"><?= lang('WhatsAppPlugin.webhook_url_help') ?></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('WhatsAppPlugin.verify_token'), 'verify_token', ['class' => 'control-label col-xs-3']) ?>
|
|
<div class="col-xs-8">
|
|
<?= form_input([
|
|
'name' => 'verify_token',
|
|
'id' => 'verify_token',
|
|
'class' => 'form-control input-sm',
|
|
'value' => esc($settings['verify_token'] ?? ''),
|
|
]) ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('WhatsAppPlugin.app_secret'), 'app_secret', ['class' => 'control-label col-xs-3']) ?>
|
|
<div class="col-xs-8">
|
|
<div class="input-group">
|
|
<span class="input-group-addon input-sm"><span class="glyphicon glyphicon-lock"></span></span>
|
|
<?= form_password([
|
|
'name' => 'app_secret',
|
|
'id' => 'app_secret',
|
|
'class' => 'form-control input-sm',
|
|
'value' => '',
|
|
'autocomplete' => 'new-password',
|
|
'placeholder' => $app_secret_configured ? lang('WhatsAppPlugin.secret_stored') : '',
|
|
]) ?>
|
|
</div>
|
|
<?php if ($app_secret_configured): ?>
|
|
<div class="checkbox">
|
|
<label>
|
|
<input type="checkbox" name="clear_app_secret" value="1"> <?= lang('WhatsAppPlugin.clear_secret') ?>
|
|
</label>
|
|
</div>
|
|
<span class="help-block"><?= lang('WhatsAppPlugin.secret_unchanged_help') ?></span>
|
|
<?php endif; ?>
|
|
<span class="help-block"><?= lang('WhatsAppPlugin.app_secret_help') ?></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xs-offset-3 col-xs-8">
|
|
<p class="help-block"><?= lang('WhatsAppPlugin.window_notice') ?></p>
|
|
</div>
|
|
|
|
<?= form_submit([
|
|
'name' => 'submit_whatsapp',
|
|
'id' => 'submit_whatsapp',
|
|
'value' => lang('Common.submit'),
|
|
'class' => 'btn btn-primary btn-sm pull-right',
|
|
]) ?>
|
|
|
|
<div class="col-xs-offset-3 col-xs-8" style="margin-top: 10px;">
|
|
<span class="glyphicon glyphicon-info-sign"> </span>
|
|
<a href="https://developers.facebook.com/documentation/business-messaging/whatsapp/get-started" target="_blank" rel="noopener noreferrer">
|
|
<?= lang('WhatsAppPlugin.docs_link') ?>
|
|
</a>
|
|
</div>
|
|
|
|
</fieldset>
|
|
</div>
|
|
<?= form_close() ?>
|
|
|
|
<script type="text/javascript">
|
|
$(document).ready(function() {
|
|
// The read-only webhook URL is display-only; keep it out of the submitted settings.
|
|
$('#webhook_url_display').prop('disabled', true);
|
|
|
|
$('#config_form').validate($.extend(form_support.handler, {
|
|
submitHandler: function(form) {
|
|
$(form).ajaxSubmit({
|
|
success: function(response) {
|
|
$.notify({ message: response.message }, { type: response.success ? 'success' : 'danger' });
|
|
if (response.success) {
|
|
$('#plugin-config-modal').modal('hide');
|
|
}
|
|
},
|
|
dataType: 'json'
|
|
});
|
|
},
|
|
|
|
errorLabelContainer: '#error_message_box',
|
|
|
|
// The token is absent here on purpose: an empty field means "keep the
|
|
// stored one", so it is required only until one has been saved, which
|
|
// the field's own class expresses.
|
|
rules: {
|
|
phone_id: 'required'
|
|
},
|
|
|
|
messages: {
|
|
phone_id: '<?= esc(lang('WhatsAppPlugin.phone_id_required'), 'js') ?>',
|
|
token: '<?= esc(lang('WhatsAppPlugin.token_required'), 'js') ?>'
|
|
}
|
|
}));
|
|
});
|
|
</script>
|