How to create a notification by yourself - LinkNacional/WHMCS-WhatsApp-API-Notifications-open-source GitHub Wiki
This documentation matches the version v4.0+ of the module.
For v3, tutorial, access here.
Summary
What is this tutorial about?
This tutorial will help you to create your own notifications.
To make your own notification you need to have programming knowledge and follow the module standards.
Creating your own notification
- Custom notification must be placed in this path:
/modules/addons/lknhooknotification/src/Notifications/Custom/ - Custom notification must have unique codes. Example: HelloWorldInvoiceCreated, NewServiceInvoice
Basic example:
Path: /modules/addons/lknhooknotification/src/Notifications/Custom/HelloWorldInvoiceCreatedNotification.php
<?php
namespace Lkn\HookNotification\Notifications\Custom;
use Lkn\HookNotification\Core\NotificationReport\Domain\NotificationReportCategory;
use Lkn\HookNotification\Core\Notification\Domain\AbstractNotification;
use Lkn\HookNotification\Core\Notification\Domain\NotificationParameter;
use Lkn\HookNotification\Core\Notification\Domain\NotificationParameterCollection;
use Lkn\HookNotification\Core\Shared\Infrastructure\Hooks;
/**
* @see https://developers.whmcs.com/hooks-reference/invoices-and-quotes/#invoicecreated
*/
final class HelloWorldInvoiceCreatedNotification extends AbstractNotification
{
public function __construct()
{
parent::__construct(
'HelloWorldInvoiceCreated',
NotificationReportCategory::INVOICE,
Hooks::INVOICE_CREATED,
new NotificationParameterCollection([
new NotificationParameter(
'invoice_id',
lkn_hn_lang('Invoice ID'),
fn (): int => $this->whmcsHookParams['invoiceid']
),
]),
fn() => getClientIdByInvoiceId($this->whmcsHookParams['invoiceid']),
fn() => $this->whmcsHookParams['invoiceid'],
);
}
}
Then, the module will load this class in the Notifications page:
When, adding a new template, you will be able to use the parameters defined inside the class:
Note that $this->whmcsHookParams is automatically filled by the module and is the content that WHMCS passes to the hook as parameters when the notification is fired.
For this example, the hook used is Hooks::INVOICE_CREATE hook, which has the following parameters:
Source: WHMCS Hooks Reference
Adding conditional logic to decide if the notification should be run
In this example, we can see that the notification NewServiceInvoice still use the Hooks::INVOICE_CREATED, but has implemented the method shouldRun(): bool.
In this method, you have access to the WHMCS hook params $this->whmcsHookParams and can write custom business rules to decided if the notification should be sent or not.
In this case, we will fire the notification only if it is associated to a client service.
<?php
/**
* Code: InvoiceReminderPdf
*/
namespace Lkn\HookNotification\Notifications;
use Lkn\HookNotification\Core\Notification\Domain\AbstractNotification;
use Lkn\HookNotification\Core\Notification\Domain\NotificationParameter;
use Lkn\HookNotification\Core\Notification\Domain\NotificationParameterCollection;
use Lkn\HookNotification\Core\NotificationReport\Domain\NotificationReportCategory;
use Lkn\HookNotification\Core\Shared\Infrastructure\Hooks;
final class NewServiceInvoiceNotification extends AbstractNotification
{
public function __construct()
{
parent::__construct(
'NewServiceInvoice',
NotificationReportCategory::INVOICE,
Hooks::INVOICE_CREATED,
new NotificationParameterCollection([
new NotificationParameter(
'invoice_id',
lkn_hn_lang('Invoice ID'),
fn (): int => $this->whmcsHookParams['invoiceid']
),
new NotificationParameter(
'invoice_items',
lkn_hn_lang('Invoice items'),
fn (): string => getItemsRelatedToInvoice($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'invoice_due_date',
lkn_hn_lang('Invoice due date'),
fn (): string => getInvoiceDueDateByInvoiceId($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'invoice_pdf_url',
lkn_hn_lang('Invoice PDF URL'),
fn (): string => getInvoicePdfUrlByInvocieId($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'invoice_balance',
lkn_hn_lang('Invoice balance'),
fn (): string => getInvoiceBalance($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'invoice_total',
lkn_hn_lang('Invoice total'),
fn (): string => getInvoiceTotal($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'invoice_subtotal',
lkn_hn_lang('Invoice subtotal'),
fn (): string => getInvoiceSubtotal($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'client_id',
lkn_hn_lang('Client ID'),
fn (): int => $this->client->id
),
new NotificationParameter(
'client_email',
lkn_hn_lang('Client email'),
fn (): string => getClientEmailByClientId($this->client->id)
),
new NotificationParameter(
'client_first_name',
lkn_hn_lang('Client first name'),
fn (): string => getClientFirstNameByClientId($this->client->id)
),
new NotificationParameter(
'client_full_name',
lkn_hn_lang('Client full name'),
fn (): string => getClientFullNameByClientId($this->client->id)
),
]),
fn() => getClientIdByInvoiceId($this->whmcsHookParams['invoiceid']),
fn() => $this->whmcsHookParams['invoiceid']
);
}
public function shouldRun(): bool
{
$invoiceId = $this->whmcsHookParams['invoiceid'];
// Checks if the invoice was generated by the CRON.
if (
$this->whmcsHookParams['source'] !== 'autogen' ||
$this->whmcsHookParams['user'] !== 'system'
) {
return false;
}
// Invoices of orders does not count.
if (is_int(getOrderIdByInvoiceId($invoiceId))) {
return false;
}
$invoiceItems = getInvoiceItems($invoiceId);
$invoiceItemRelatedToProduct = array_filter(
$invoiceItems,
function (array $item): bool {
return !empty($item['product_id']);
}
);
if (count($invoiceItemRelatedToProduct) > 0) {
return true;
}
return false;
}
}
The shouldRun(): bool method is optional to be implemented and must return true, meaning the notification should be sent, or false otherwise.
Cron-based notifications
Cron-based notifications are notifications that use one of the cron hooks:
Example:
<?php
namespace Lkn\HookNotification\Notifications\Custom;
use DateTime;
use Exception;
use Lkn\HookNotification\Core\NotificationReport\Domain\NotificationReportCategory;
use Lkn\HookNotification\Core\Notification\Domain\AbstractCronNotification;
use Lkn\HookNotification\Core\Notification\Domain\NotificationParameter;
use Lkn\HookNotification\Core\Notification\Domain\NotificationParameterCollection;
use Lkn\HookNotification\Core\Shared\Infrastructure\Hooks;
use WHMCS\Database\Capsule;
final class Invoice6DaysLateNotification extends AbstractCronNotification
{
public function __construct()
{
$parameters = [
new NotificationParameter(
'invoice_id',
lkn_hn_lang('invoice_id'),
fn (): int => $this->whmcsHookParams['invoice_id'],
),
new NotificationParameter(
'invoice_balance',
lkn_hn_lang('invoice_balance'),
fn (): string => getInvoiceBalance($this->whmcsHookParams['invoice_id'])
),
new NotificationParameter(
'invoice_total',
lkn_hn_lang('invoice_total'),
fn (): string => getInvoiceTotal($this->whmcsHookParams['invoice_id'])
),
new NotificationParameter(
'invoice_subtotal',
lkn_hn_lang('invoice_subtotal'),
fn (): string => getInvoiceSubtotal($this->whmcsHookParams['invoice_id'])
),
new NotificationParameter(
'invoice_due_date',
lkn_hn_lang('invoice_due_date'),
fn (): string => getInvoiceDueDateByInvoiceId($this->whmcsHookParams['invoice_id'])
),
new NotificationParameter(
'invoice_items',
lkn_hn_lang('invoice_items'),
fn (): string => $this->getInvoiceIdAndFirstItsFirstItem(),
),
new NotificationParameter(
'invoice_id_and_first_item',
lkn_hn_lang('invoice_id_and_first_item'),
fn (): string => getInvoicePdfUrlByInvocieId($this->whmcsHookParams['invoice_id'])
),
new NotificationParameter(
'client_id',
lkn_hn_lang('Client ID'),
fn (): int => $this->client->id
),
new NotificationParameter(
'client_email',
lkn_hn_lang('Client email'),
fn (): string => getClientEmailByClientId($this->client->id)
),
new NotificationParameter(
'client_first_name',
lkn_hn_lang('Client first name'),
fn (): string => getClientFirstNameByClientId($this->client->id)
),
new NotificationParameter(
'client_full_name',
lkn_hn_lang('Client full name'),
fn (): string => getClientFullNameByClientId($this->client->id)
),
];
$asaasTable = Capsule::schema()->hasTable('mod_cobrancaasaasmpay');
if ($asaasTable) {
$parameters['invoice_pdf_url_asaas_pay'] = new NotificationParameter(
'invoice_pdf_url_asaas_pay',
lkn_hn_lang('invoice_pdf_url_asaas_pay'),
fn () => $this->getAsaasPayUrl()
);
}
parent::__construct(
'Invoice6DaysLate',
NotificationReportCategory::INVOICE,
Hooks::DAILY_CRON_JOB,
new NotificationParameterCollection($parameters),
fn() => $this->whmcsHookParams['client_id'],
fn() => $this->whmcsHookParams['report_category_id'],
);
}
public function getPayload(): array
{
$invoices = localAPI('GetInvoices', [
'limitnum' => 1000,
'status' => 'Overdue',
]);
$payloads = [];
foreach ($invoices['invoices']['invoice'] as $invoice) {
$givenDateTime = new DateTime($invoice['duedate']);
$currentDateTime = new DateTime();
$interval = $currentDateTime->diff($givenDateTime);
if (
$interval->days !== 6
|| $invoice['paymentmethod'] === 'freeproducts'
|| $invoice['total'] === '0.00'
) {
continue;
}
$invoiceId = $invoice['id'];
$clientId = $invoice['userid'];
$payloads[] = [
'client_id' => $clientId,
'report_category_id' => $invoiceId,
'invoice_id' => $invoiceId,
];
}
return $payloads;
}
private function getAsaasPayUrl()
{
$invoicePayMethod = Capsule::table('tblinvoices')->where('id', $this->whmcsHookParams['invoice_id'])->first('paymentmethod')->paymentmethod;
if ($invoicePayMethod !== 'cobrancaasaasmpay') {
throw new Exception('Invoice does not belong to cobrancaasaasmpay gateway.');
}
$asaasPayBoletoUrl = Capsule::table('mod_cobrancaasaasmpay')->where('fatura_id', $this->whmcsHookParams['invoice_id'])->first('url_boleto')->url_boleto;
if (empty($asaasPayBoletoUrl)) {
throw new Exception('Could not get Asaas URL.');
}
return str_replace('/b/pdf/', '/i/', $asaasPayBoletoUrl);
}
private function getInvoiceIdAndFirstItsFirstItem(): string
{
$invoiceId = $this->whmcsHookParams['invoice_id'];
return "$invoiceId " . getInvoiceItemsDescriptionsByInvoiceId($invoiceId)[0];
}
}
Cron notifications must inherit the abstract class AbstractCronNotification and implement the getPayload(): array method.
The Invoice6DaysLate notification above has implemented the method getPayload(): array that will return a list of WHMCS hook params. Each sublist in the list will be passed to the notification as if it were $this->whmcsHookParams. So you can use getPayload(): array to generate the parameters that will feed the notification.
Manual Notifications
Manual notifications are the ones that use output hooks:
These notifications are only fired manually by an admin.
These notifications must extend AbstractManualNotification.
<?php
namespace Lkn\HookNotification\Notifications;
use Lkn\HookNotification\Core\Notification\Domain\AbstractManualNotification;
use Lkn\HookNotification\Core\Notification\Domain\NotificationParameter;
use Lkn\HookNotification\Core\Notification\Domain\NotificationParameterCollection;
use Lkn\HookNotification\Core\NotificationReport\Domain\NotificationReportCategory;
use Lkn\HookNotification\Core\Shared\Infrastructure\Hooks;
final class InvoiceReminderNotification extends AbstractManualNotification
{
public function __construct()
{
parent::__construct(
'InvoiceReminder',
NotificationReportCategory::INVOICE,
Hooks::ADMIN_INVOICES_CONTROLS_OUTPUT,
new NotificationParameterCollection([
new NotificationParameter(
'invoice_id',
lkn_hn_lang('Invoice ID'),
fn (): int => $this->whmcsHookParams['invoiceid']
),
new NotificationParameter(
'invoice_items',
lkn_hn_lang('Invoice items'),
fn (): string => getItemsRelatedToInvoice($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'invoice_due_date',
lkn_hn_lang('Invoice due date'),
fn (): string => getInvoiceDueDateByInvoiceId($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'invoice_pdf_url',
lkn_hn_lang('Invoice PDF URL'),
fn (): string => getInvoicePdfUrlByInvocieId($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'invoice_balance',
lkn_hn_lang('Invoice balance'),
fn (): string => getInvoiceBalance($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'invoice_total',
lkn_hn_lang('Invoice total'),
fn (): string => getInvoiceTotal($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'invoice_subtotal',
lkn_hn_lang('Invoice subtotal'),
fn (): string => getInvoiceSubtotal($this->whmcsHookParams['invoiceid'])
),
new NotificationParameter(
'client_id',
lkn_hn_lang('Client ID'),
fn (): int => $this->client->id
),
new NotificationParameter(
'client_email',
lkn_hn_lang('Client email'),
fn (): string => getClientEmailByClientId($this->client->id)
),
new NotificationParameter(
'client_first_name',
lkn_hn_lang('Client first name'),
fn (): string => getClientFirstNameByClientId($this->client->id)
),
new NotificationParameter(
'client_full_name',
lkn_hn_lang('Client full name'),
fn (): string => getClientFullNameByClientId($this->client->id)
),
]),
fn() => getClientIdByInvoiceId($this->whmcsHookParams['invoiceid']),
fn() => $this->whmcsHookParams['invoiceid']
);
}
}
In the example above, the InvoiceReminder notification uses the output hook Hooks::ADMIN_INVOICES_CONTROLS_OUTPUT.
If enabled, the notification will be show at the admin invoice page:
All kinds of notifications can implement the method shouldRun(): bool.
Notification title and parameter translation
Custom translation must be placed at: lknhooknotification/src/Notifications/Custom/lang/
- To translate a notification title, you should do:
Custom/lang/english.php
Custom/lang/portugues-br.php
- To translate a parameter, you can call the
lkn_hn_langfunction: