Skip to content

How to create a notification by yourself

Bruno Ferreira edited this page Dec 20, 2023 · 13 revisions

This repository uses VS Code Dev Container extension for its development process.

Also, you can access the technical documentation of the module to familiarize with the classes, methods and functions provided: Technical Documentation

Recommended readings:

Setup development environment

  1. Start by cloning this repository onto your local machine.

  2. Before proceeding, ensure that Docker is installed on your system. If you don't have Docker installed, you can download it from here.

    If you are using Windows, it is recommended to install WSL for Docker Desktop. Detailed instructions for installation can be found here.

  3. The repository employs an FTP extension that automatically uploads files to WHMCS upon saving. You will find a file named .vscode/sftp.example.json within the repository. Rename this file to sftp.json and put your FPT credentials there.

    For more information on using this extension, refer to the documentation available here.

  4. Next, install the necessary Dev Container extensions in your VSCode. You can find these extensions in the Visual Studio Code Marketplace here.

    And for WSL.

  5. Open the command palette in VSCode by pressing CTRL + Shift + P. Type "Reopen in Container" and select it from the options that appear, then press Enter.


By following these steps, you will have successfully set up the development environment.

Notification creation tutorial

To familiarize yourself with the process of creating a notification, you can open one of the existing notification folders in the repository and explore its structure and components. Access notifications.

Furthermore, the repository includes the stubs.php file, which not only provides information about the available classes and methods of the module but also enables VSCode to display method and class previews as you type.

Additionally, you can access the documentation of the available classes and methods here.

If you intend to contribute your own notification to this repository, you can make and push your commits to the dev branch and then initiate a pull request from the dev branch to the main branch.

We will be using a notification that runs when a order is created in WHMCS:

<?php

/**
 * Code: OrderCreated
 */

namespace Lkn\HookNotification\Notifications\WhatsApp\OrderCreated;

use Lkn\HookNotification\Config\Hooks;
use Lkn\HookNotification\Config\ReportCategory;
use Lkn\HookNotification\Domains\Platforms\WhatsApp\AbstractWhatsAppNotifcation;
use Lkn\HookNotification\Helpers\Logger;

final class OrderCreatedNotification extends AbstractWhatsAppNotifcation
{
    public string $notificationCode = 'OrderCreated';
    public Hooks|array|null $hook = Hooks::AFTER_SHOPPING_CART_CHECKOUT;

    public function run(): bool
    {
        $orderId = $this->hookParams['OrderID'];

        // Setup properties for reporting purposes (not required).
        $this->setReportCategory(ReportCategory::ORDER);
        $this->setReportCategoryId($orderId);

        // Setup client ID for getting its WhatsApp number (required).
        $clientId = $this->getClientIdByInvoiceId($this->hookParams['InvoiceID']);
        $this->setClientId($clientId);

        $fraudCheckResponse = localAPI('OrderFraudCheck', ['orderid' => $orderId]);

        if (isset($fraudCheckResponse['status']) && $fraudCheckResponse['status'] === 'Fail') {
            Logger::log(
                $this->getNotificationLogName(),
                [
                    'msg' => 'Abort for order was identified as fraud',
                    'instance' => $this
                ],
                ['fraud_check' => $fraudCheckResponse]
            );

            return false;
        }

        // Send the message and get the raw response (converted to array) from WhatsApp API.
        $response = $this->sendMessage();

        // Defines if response tells if the message was sent successfully.
        $success = isset($response['messages'][0]['id']);

        return $success;
    }

    public function defineParameters(): void
    {
        $this->parameters = [
            'order_id' => [
                'label' => $this->lang['order_id'],
                'parser' => fn () => $this->hookParams['OrderID'],
            ],
            'order_items_descrip' => [
                'label' => $this->lang['order_items_descrip'],
                'parser' => fn () => self::getOrderItemsDescripByOrderId($this->hookParams['OrderID'])
            ],
            'client_first_name' => [
                'label' => $this->lang['client_first_name'],
                'parser' => fn () => $this->getClientFirstNameByClientId($this->clientId),
            ],
            'client_full_name' => [
                'label' => $this->lang['client_full_name'],
                'parser' => fn () => $this->getClientFullNameByClientId($this->clientId),
            ]
        ];
    }
}
  1. Note that the notification must have docblock on the top of the file in the format "Code: {NotificationName}":
<?php

/**
 * Code: OrderCreated
 */
  1. Then, the notifications extends from AbstractWhatsAppNotifcation and must set these two properties $notificationCode and $hook as defined in step 1:
final class OrderCreatedNotification extends AbstractWhatsAppNotifcation
{
    public string $notificationCode = 'OrderCreated';
    public Hooks|array|null $hook = Hooks::AFTER_SHOPPING_CART_CHECKOUT;
  • AbstractWhatsAppNotification provides specific methods and properties for a WhatsApp notification. Access AbstractWhatsAppNotification docs.
  • $hooks is one of WHMCS hooks by which the notification must be executed. A notification can be run on different hooks:
public Hooks|array|null $hook = [Hooks::CANCEL_ORDER, Hooks::CANCEL_AND_REFUND_ORDER];
  • $notificationCode is the identifier of this notification, must be unique and must have the same name as the notification folder.
  1. Then, the notification implements the required run method.
public function run(): bool
{
    $orderId = $this->hookParams['OrderID'];

    // Setup properties for reporting purposes (not required).
    $this->setReportCategory(ReportCategory::ORDER);
    $this->setReportCategoryId($orderId);

    // Setup client ID for getting its WhatsApp number (required).
    $clientId = $this->getClientIdByInvoiceId($this->hookParams['InvoiceID']);
    $this->setClientId($clientId);

    $fraudCheckResponse = localAPI('OrderFraudCheck', ['orderid' => $orderId]);

    // Only sends the notification for order that has passed the fraud check.
    if (isset($fraudCheckResponse['status']) && $fraudCheckResponse['status'] === 'Fail') {
        Logger::log(
            $this->getNotificationLogName(),
            [
                'msg' => 'Abort for order was identified as fraud',
                'instance' => $this
            ],
            ['fraud_check' => $fraudCheckResponse]
        );

        return false;
    }

    // Send the message and get the raw response (converted to array) from WhatsApp API.
    $response = $this->sendMessage();

    // Defines if response tells if the message was sent successfully.
    $success = isset($response['messages'][0]['id']);

    return $success;
}

The run method:

  • Should set report properties that will help keeping relatories of sent notifications.
  • Should set the client ID that owns the order.
  • Can perform specific conditions like if the order passed the fraud check in order to send or not the message.
  • Can call the $this->sendMessage() that automatically fetches the client WhatsApp number and sends the notification.
  • Must determine if the notification was received by the WhatsApp API and return true or false properly.

Note that the $this->hookParams property holds the hook parameters passed by WHMCS to the notification. In this example, the hook parameters are the ones of the WHMCS hook AfterShoppingCartCheckout as defined in the $hook property.

  1. After that, you should set the parameters that the notification will support using the defineParameters method.

For a WhatsApp notification, the term "parameter" refers to the specific values you want to substitute for the placeholder keys {{1}}, {{2}} that made when you created the message template, and so on. These parameters are dynamic pieces of information that personalize the notification content when it's sent to different recipients.

Ensure your notification follows the structure below of the parameters array.

public function defineParameters(): void
{
    $this->parameters = [
        'order_id' => [
            'label' => $this->lang['order_id'],
            'parser' => fn () => $this->hookParams['OrderID'],
        ],
        'order_items_descrip' => [
            'label' => $this->lang['order_items_descrip'],
            'parser' => fn () => self::getOrderItemsDescripByOrderId($this->hookParams['OrderID'])
        ],
        'client_first_name' => [
            'label' => $this->lang['client_first_name'],
            'parser' => fn () => $this->getClientFirstNameByClientId($this->clientId),
        ],
        'client_full_name' => [
            'label' => $this->lang['client_full_name'],
            'parser' => fn () => $this->getClientFullNameByClientId($this->clientId),
        ]
    ];
}

AbstractWhatsAppNotifcation uses the NotificationParamParseTrait for getting the parameters values later.

Observe that the paramters follow a structure of the label and the parser that gets the values for the notification.

How to create a new parameter

If this trait does not have the method you need, you can simply implement one in the notification itself:

// Add the new parameter to the defineParameters method:

public function defineParameters(): void
{
    $this->parameters = [
        // Other parameters...
        'order_date' => [
            'label' => $this->lang['order_date'],
            'parser' => fn () => $this->getOrderDate($this->hookParams['OrderID']),
        ]
    ];
}

// Create the parser for order_date parameter.

/**
 * @param int $orderId
 *
 * @return string
 */
private function getOrderDate(int $orderId): string {
    // Use WHMCS Capsule to interact with the database: https://developers.whmcs.com/advanced/db-interaction/#the-query-manager
    return Capsule::table('tblorders')->where('id', $orderId)->value('date');
}

The labels of the parameters will appear in the notification setup page as show below.

image

  1. Finally, inside each notification folder, there is a hooks.php with the code responsible for registering the notification in the module:
<?php

use Lkn\HookNotification\Domains\Notifications\Messenger;
use Lkn\HookNotification\Notifications\WhatsApp\OrderCreated\OrderCreatedNotification;

Messenger::run(OrderCreatedNotification::class);

You should use the Messenger class in order to the notification work properly when active.

  1. Now, to activate the notification, go to the module settings page and access the page below to setup the notification with a message template from WhatsApp.

image

Then, searh for "Order created" and click on activate: image

And configure the notification:

image

If you wish to test this notification, access the client area and finish a checkout of a service.

Internationalization

Notification on this repository will support English, Portuguese (BR) and Portuguse (PT).

Inside each notification folder, there is a lang folder that holds the translations for the notification title, description and parameters:

image

A language file structure:

<?php

$lang['notification_title'] = 'Order created';
$lang['notification_description'] = 'Runs when the client completes a checkout and the order is created.';

// Parameters labels

$lang['order_id'] = 'Order ID';
$lang['order_items_descrip'] = 'Order items';
$lang['client_first_name'] = 'Client first name';
$lang['client_full_name'] = 'Client full name';

return $lang;

The only translation required if for notification_title. If it is not provided, the notification won't show proproperly on the list.

image

For notifications parameters, their labels are called in the defineParameters method using the $this-lang property that automatically loads the language file according to the Language setting.

image

public function defineParameters(): void
    {
        $this->parameters = [
            'order_id' => [
                'label' => $this->lang['order_id'],
                'parser' => ...,
            ],
            'order_items_descrip' => [
                'label' => $this->lang['order_items_descrip'],
                'parser' => ...
            ],
            'client_first_name' => [
                'label' => $this->lang['client_first_name'],
                'parser' => ...,
            ],
            'client_full_name' => [
                'label' => $this->lang['client_full_name'],
                'parser' => ...,
            ]
        ];
    }

If you use the module only in english, you can simply create the english.php and provide the necessary translation.

More examples on creating notifications

Specific conditions

If you need more examples on use cases for notifications, please refer to the OrderPendingFor3DaysNotification.

This is a notification that runs on the DailyCronJob hook and send a message to clients that have an pending order for three days.

Manual notification

Manual notifications are triggered by an admin and have a visual output like the InvoiceReminderPdfNotification.

image

If you wish to make a notification like this, please expore the InvoiceReminderNotification folder and the files located in this folder that handles the display of the button: /modules/addons/lknhooknotification/src/resources/notifications/hooks/AdminInvoicesControlsOutput.

Report and events

By default, the module automatically save reports about the notifications that are sent and triggers some events like sending a private note to Chatwoot.

In the run() method of you notification, you need to provide the client id and the report category as follow:

$this->setReportCategory(ReportCategory::INVOICE);
$this->setReportCategoryId($this->hookParams['invoiceid']);

Also, in the run() method, you need to find out the client ID, considering your context:

$clientId = $this->getClientIdByInvoiceId($this->hookParams['invoiceid']);

$this->setClientId($clientId);

Disable reports and events

if (!$this->mustRun()) {
    $this->events = [];
    $this->enableAutoReport = false;

    return false;
}