BILLmanager 6 Startup, Advanced

How to add a notification module

BILLmanager allows you to add notifications for staff members and customers and notification gateways in order to add more functionality to your billing panel.

By default BILLmanager supports three types of notifications:

  • SMS  notifications — the billing panel sends SMS to phone numbers that users provide in their profile.
    • The system can only send notifications 
    • The system sends notifications via gateways 
  • Email notifications — notifications are sent to user email (some notifications can be sent only to email as they use specific functions).
    • The system can send and receive notifications 
    • The system sends and receives notifications via gateways 
  • Notifications in the Notifications module in the Client area. They are generated based on emails that BILLmanager sends to customers.
    • The system can only send notifications 
    • Gateways are not supported 

All files of modules and gateways are called with system calls or in the background. You can write them in any programming language that supports input/output strings.

How it works

The procedure involves the following steps:

  • The system checks that a client is subscribed to the notification type
  • Generates an XML notification of a required type
  • Sends the XML containing a notification template, notification XML, and provider and user  information

The following operations are performed in the module:

  • The system defines a notification type based on its structure.
    • For XSLT templates, writes the template to the XML notification
    • For EJS templates, converts the XML into JSON and sends it to the EJS template engine together with the template
  • Changes the macros in the resulting text, if needed
  • If notifications gateways are not supported, the module will send notifications
  • If gateways are supported, the system selects a gateway based on the provider id and a notifications module type
  • The gateway parameters, contact data, message text, and its heading are sent to the gateway module.

Module architecture 

Every notification or gateway module should be able to process the commands described below. If a command is not supported or if the module outputs data in an unsupported format, BILLmanager won't interact with that module.

Notification module architecture 

The module is installed into /usr/local/mgr5/notify/. It should be able to process the following commands:

  • --command process — processes a queue of notifications to be sent. The notifications queue is kept in the notifytask table and has the following structure:
    • id — notification id. It is generated automatically.
    • modulename — notification module type name. Standard values: ntemail ntsms ntinternal
    • filename — notification file name
    • priority — sending priority. We recommend that you select several notifications in descending order of priority. In case of mass mailing when you send a large number of notifications, they will be delivered in time.
    • error_count — the number of attempts to send a notification that failed
    • forcedonothing — enables to send a notification regardless the billmgr.DoNothing file which is created during data transfer from another billing panel.
    • err_info — number of failed attempts to send notifications
    • createdate — date when the notification was added into the queue
  • --command getmessage --gate gate_id, where gate_id is the gateway id. The all value can be sent as the --gate parameter. In this case, notifications should be processed by all gateways of the notifications type.
  • --command features — call parameters of the notification module. The module should send an XML description of supported functions to stdout. The XML format:
<doc>
  <features>
    <feature name="html"/> 
    <feature name="sms"/> 
    <feature name="call"/> 
  </features>
  <contact_type>contact type</contact_type> 
</doc>

Developers can define a queue processing procedure and operations with received messages. For example, you can use the following scheme:

  • The module periodically checks new messages in the queue
  • Adding new emails into the ticketing system 
  • Managing services via the incoming emails
  • Requesting and outputting information by control commands

If you write the module with header files, the following methods should be implemented

  • virtual mgr_xml::Xml Features() const = 0; — returns XML description of supported features. The data will be automatically sent to stdout
  • virtual bool UserNotify(const string& filename) const = 0; — sends a notification described in the file with the name, which was passed in the parameter
  • virtual void GetMessage(string gate_id = 0) const = 0; — processes incoming messages for the gateway with gate_id

The following method can be overwritten: 

  • virtual int ProcessQueue() const; — processes the notifications queue. UserNotify calls a class and can be empty if the logic is specified in ProcessQueue

Gateway module architecture 

The module is installed into /usr/local/mgr5/gate/. It should be able to process the following commands:

  • --command features — call parameters of the gateway module. The module should send an XML description of supported functionality to stdout. The XML document must have the following format:
<doc>
  <features>
    <feature name="outgoing"/> 
    <feature name="ingoing"/> 
    <feature name="formtune"/> 
    <feature name="check_connection"/> 
  </features>
  <notify_module>notification module type</notify_module> 
</doc>
  • --command formtune — modifies the gateway parameter edit form. The XML of the gateway edit form is sent to stdin and the modified XML of the edit form description is returned to stdout.
  • --command check_connection — checks the connection to the gateway with specified parameters. The XML description of gateway parameters form with data that was entered into that form is sent to stdin. The XML description of the configuration form should be sent to stdout (you can modify the XML if needed).
  • --command outgoing and --command ingoing — BILLmanager doesn't call them directly, except for SMS - in this case --command outgoing must be specified in a certain format. In all other cases, you can describe any commands that the notification module calls. You can find a description of how the standard module works with those parameters: 
  • --command outgoing — send notifications. The following XML will be sent to module input:
<doc>
  <gateway> - gateway parameters
    <param>value</param>
    <param>value</param>
    ...
    <param>value</param>
    <xmlparams>Gateway connection parameters in the form of XML</xmlparams>
  </gateway>
  <message>message text</message>
  <user> - parameters of the user the notification is sent to
    <param>value</param>
    <param>value</param>
    ...
    <param>value</param>
  </user>
  <project> - provider parameters 
    <param>value</param>
    <param>value</param>
    ...
    <param>value</param>
  </project>
</doc>

The module should send an empty XML or error XML description to stdout. 

  • --command ingoing (it is used to receive mail) — the XML with gateway parameters as described above will be passed to stdin. The XML with a list of received messages will be passed to stdout. 
<doc>
  <messages>
    <message>source text</message>
    ...
    <message>source text</message>
  </messages>
</doc>

Implementation of the module with BILLmanager header files assumes that the following methods are supported: 

  • virtual mgr_xml::Xml Features() const = 0 — XML description of supported features;
  • virtual mgr_xml::Xml Ingoing(mgr_xml::Xml& input) const = 0 — XML with gateway parameters is sent to stdin (parameters can be obtained with the GateParam method), a list of messages in the described format is sent to stdout;
  • virtual void Outgoing(mgr_xml::Xml& input) const = 0 — XML with gateways parameters and messages to be sent are passed to stdin (you can obtain the parameters with the GateParam method).

Examples

C++ (using BILLmanager libraries in the developer package)

Starting from version 5.58.0 you can use BILLmanager header files to develop custom processing modules. Besides a simplified example, you can look into examples in the BILLmanager developer package - billmanager-[BILLmanager version]-devel:

yum install billmanager-devel
Click here to expand...

You can find the examples in the following directory:

/usr/local/mgr5/src/examples

C++ (using BILLmanager libraries)

Follow the link below to find a module for notifications and XMPP gateway 

  https://github.com/ISPsystemLLC/jabber

The example is written on C++ using COREmanager and BILLmanager header files, as well as libraries Gloox. The example contains:

  • The  ntjabber notifications module — the main file  ntjabber.cpp.  It is  responsible for notification type, allows to add notifications templates and  create mass mailing of a required type
  • The gateway module for connecting to the  Jabber server gwjabber - the main file gwjabber.cpp. It is responsible for sending messages to a user Jabber contact and handling incoming messages.
  • XML files that describe required messages and server connection parameters: 
    • billmgr_mod_ntjabber.xml — add a Jabber contact field on the user edit form (values are displayed and saved automatically according to mechanism described  here), adds a description of the notification type
    • billmgr_mod_gwjabber.xml — describes the connection parameters to jabber-server, and also describes the name of the connection module
  • Description of the jabber database field — describes an additional field for the user table in BILLmanager database
  • Logo of the billmanager-plugin-gwjabber.png gateway — enables to display the  XMPP logo on the gateway type selection form. 
  • The Makefile description file.

Other programming languages 

Module XML 

The XML description is saved into the /usr/local/mgr5/etc/xml/billmgr_mod_ntxxx.xml file for notifications module, and into the /usr/local/mgr5/etc/xml/billmgr_mod_gwxxx.xml file for gateway modules, where xxx is a unique name of the module. In the following example, you can find the XML description of the gateway module. The XML file of the notifications module is described in the "Example in C++" section.

The file has the following format (an example of integration with ePochta SMS)

<mgrdata>
  <plugin name="gwepochta">           <!-- plug-in description  in BILLmanager -->
    <group>gateway</group>            <!-- email gateways associated with that plug-in -->
    <author>BILLmanager team</author> <!-- module developer -->
  </plugin>
  <metadata name="gateway.gwepochta"> <!-- description of module settings -->
    <form>
      <field name="login">
        <input type="text" name="login" required="yes" identifier="yes"/>
      </field>
      <field name="password">
        <input type="password" name="password" required="yes"/>
      </field>
      <field name="sender">
        <input type="text" name="sender" required="yes"/>
      </field>
    </form>
  </metadata>
  <lang name="ru">
    <messages name="plugin">         <!-- message for plug-in description -->
      <msg name="desc_short_gwepochta">ePochta SMS</msg>
      <msg name="desc_full_gwepochta">ePochta SMS</msg>
      <msg name="price_gwepochta">Free</msg>
    </messages>
    <messages name="gateway.gwepochta"> <!-- message for module settings form -->
      <msg name="login">Login</msg>
      <msg name="password">Password</msg>
      <msg name="sender">Sender</msg>
      <msg name="hint_login">Login to the ePochta SMS client area</msg>
      <msg name="hint_password">Password to the client are</msg>
      <msg name="hint_sender">Sender signature</msg>
    </messages>
    <messages name="gateway_include"> <!-- a module name that will be displayed in BILLmanager -->
      <msg name="module_gwepochta">ePochta SMS server</msg>
      <msg name="gwepochta">ePochta SMS</msg>
      <msg name="desc_gwepochta">ePochta SMS</msg>
    </messages>
  </lang>
  <lang name="en"> <!-- English localization -->
    <messages name="plugin">
      <msg name="desc_short_gwepochta">ePochta SMS</msg>
      <msg name="desc_full_gwepochta">Server ePochta SMS</msg>
      <msg name="price_gwepochta">Free</msg>
    </messages>
  </lang>
</mgrdata>

Go

package main

import "bytes"
import "log"
import "encoding/xml"
import "flag"
import "fmt"
import "os"
import "io/ioutil"
import "net/http"

func request(operation, username, password, phone, message, sender string) (string, string) {
  type SMS struct {
    XMLName    xml.Name  `xml:"SMS"`
    Operation  string    `xml:"operations>operation"`
    Username  string    `xml:"authentification>username"`
    Password  string    `xml:"authentification>password"`
    Message    string    `xml:"message>text"`
    Sender    string    `xml:"message>sender"`
    Number    string    `xml:"numbers>number"`
  }
  
  v := &SMS{
      Operation:   operation,
      Username:  username,
      Password:  password,
      Message:  message,
      Sender:    sender,
      Number:    phone,
  }

  output, err := xml.MarshalIndent(v, "  ", "    ")
  log.Print("REQUEST: " + string(output))
    
  if err != nil {
    return "", ""
  }
  
  resp, err := http.Post("http://api.myatompark.com/members/sms/xml.php", "image/jpeg", bytes.NewBuffer(output))
  
  if err != nil {
    return "", ""
  }
  
  defer resp.Body.Close()
  body, err := ioutil.ReadAll(resp.Body)
  
  log.Print("RESPONSE: " + string(body))
  
  type Response struct {
    XMLName    xml.Name  `xml:"RESPONSE"`
    Status    int      `xml:"status"`
    }
  
  r := Response{Status: 1}
  resperr := xml.Unmarshal(body, &r)
  if resperr != nil {
    log.Printf("error: %v", resperr)
    return "", ""
  }
  
  if r.Status != 0 {
    return string(body), "error"
  }
  
  return string(body), ""
}

func main() {
  f, _ := os.OpenFile("var/gwepochta.log", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
  defer f.Close()
  
  log.SetOutput(f)
    
  command_ptr := flag.String("command", "features", "gateway command")

  flag.Parse()

  if *command_ptr == "features" {
    type Feature struct {
      XMLName  xml.Name  `xml:"feature"`
      Name  string    `xml:"name,attr"`
    }

    type Features struct {
      XMLName    xml.Name   `xml:"doc"`
      Features   []Feature    `xml:"features>feature"`
      Module    string    `xml:"notify_module"`
    }

    v := &Features{
        Module: "ntsms",
        Features: []Feature {
                {Name: "formtune"},
                {Name: "check_connection"},
                {Name: "outgoing"},
        },
      }

    output, err := xml.MarshalIndent(v, "  ", "    ")
    if err != nil {
      fmt.Println("error: %v\n", err)
    }

    os.Stdout.Write(output)
  } else if *command_ptr == "formtune" {
    bytes, _ := ioutil.ReadAll(os.Stdin)
    os.Stdout.Write(bytes)
  } else if *command_ptr == "check_connection" {
    type Doc struct {
      XMLName   xml.Name   `xml:"doc"`
      XMLparams    string    `xml:"xmlparams"`
      Login    string    `xml:"login"`
      Password  string    `xml:"password"`
    }
    
    bytes, _ := ioutil.ReadAll(os.Stdin)
    
    v := Doc{XMLparams: "none", Login: "none", Password: "none"}
    err := xml.Unmarshal(bytes, &v)
    if err != nil {
      log.Printf("error: %v", err)
      return
    }
    
    paramerr := xml.Unmarshal([]byte(v.XMLparams), &v)
    if paramerr != nil {
      log.Printf("error: %v", paramerr)
      return
    }

    _, error := request("BALANCE", v.Login, v.Password, "", "", "")
    
    if error != "" {
      type Error struct {
        Type  string  `xml:"type,attr"`
      }
      
      type Doc struct {
        XMLName   xml.Name   `xml:"doc"`
        ErrorType    Error    `xml:"error"`
      }
      
      t := &Error {
        Type: error,
      }
      
      v := &Doc{
        ErrorType:   *t,
      }

      output, _ := xml.MarshalIndent(v, "  ", "    ")
      os.Stdout.Write(output)
      return
    }
    os.Stdout.Write(bytes)
  } else if *command_ptr == "outgoing" {
    bytes, _ := ioutil.ReadAll(os.Stdin)
    log.Print(string(bytes))
    
    type Doc struct {
      XMLName   xml.Name   `xml:"doc"`
      XMLparams    string    `xml:"gateway>xmlparams"`
      Login    string    `xml:"login"`
      Password  string    `xml:"password"`
      Sender    string    `xml:"sender"`
      Message    string    `xml:"message"`
      Phone    string    `xml:"user>phone"`
    }
    
    v := Doc{XMLparams: "none", Login: "none", Password: "none", Message: "none", Phone: "none"}
    err := xml.Unmarshal(bytes, &v)
    
    log.Print(v.XMLparams)
    
    if err != nil {
      log.Printf("error: %v", err)
      return
    }
    
    xml.Unmarshal([]byte(v.XMLparams), &v)
    
    request("SEND", v.Login, v.Password, v.Phone, v.Message, v.Sender)
  }
}