Freeswitch: mod_nibblebill

mod_nibblebill это реализация биллинга для Freeswitch.
Модуль предназначен для списания средств со счета абонента в режиме реального времени (непосредственно во время вызова.)
Модуль взаимодействует непосредственно с ядром FS и может настраиваться и управляться при помощи команд диалплана и API.
Следующие возможности поддерживаются:

  • Дебет или Кредит аккаунт в режиме реального времени.
  • Разные тарифы для одного вызова.
  • Предупреждение звонящему о низком балансе. ( в канале вызова )
  • Отключение или перенаправление вызова при полном исчерпании средств.
  • Все вышеперечисленные функции могут обслуживать множество одновременных вызовов.

Переменные

Эти переменые канала могут быть назначены в настройках аккаунта пользователя (директории) или в диалплане.

nibble_account — Номер счета для списания средств при вызове.

nibble_increment — Расчетный отрезок времени в секундах, на который выставляется счет по тарифу nibble_rate.

nibble_minimum — Фиксированная минимальная сумма оплаты для вызова, дополнительно к nibble_rate.

nibble_rate — Стоимость одного отрезка времени nibble_increment.

nibble_rounding — Число знаков после запятой для полной стоимости вызова (округление), по его заверщении, но до прибавления nibble_minimum, если таковое задано.

nibble_total_billed — (read-only) Общая сумма которая будет списана со счета после завершения звонка.

Сценарии использования

Позволяет производить вызовы, пока внесенная на аккаунт сумма не исчерпана полностью.
Когда деньги подходят к концу проигрываеся тон или голосовое сообщение.
Когда деньги заканчиваются полностью, вызов может быть перенаправлен на указанное расширение или просто разъединен.

Если поле БД позволяет, вы можете разрешить пользователям уходить в глубокий минус. Оплата в данном случае может производится уже после использования.
Однако можно защититься от злоупотреблений указав нижнее значение кредита для аккаунта (например задать нижний порог списания средств в месяц).

Вы можете предоставлять услуги по требованию с оплатой во время вызова, фиксированной суммой или поминутно, после ввода, например, номера кредитной карты.

Вы можете настроить поле кредита, которое истощается пользователями, по принципу предоплаты выше. Когда они используют все свои кредитные средства за один день/неделю/месяц/и т. д. и не смогут больше совершать вызовы, можно будет увеличить кредит на счету на определенный период.

Что-то вроде «100 минут в день бесплатно» или других подобных акциях.

Установка и настройка

mod_nibblebill поставляется с исходным кодом Freeswitch. Модуль требует поддержки core ODBC.

Установка mod_nibblebill:

  • раскомментируйте applications/mod_nibblebill в modules.conf.
  • перекомпилируйте FS с поддержкой ODBC, если это не так. Для получения дополнительной информации о core odbc смотрите: Freeswitch CoreDb PostgreSQL
  • отредактируйте настройки в conf/autoload_configs/nibblebill.conf.xml .

Настройки nibblebill.conf.xml:

Первое что вам понадобится, это параметры подключения к БД.

Пример dsn для полключения к PostgreSQL:

<param name="odbc-dsn" value="pgsql://hostaddr=127.0.0.1 dbname=DB_NAME user=DB_USER password='DB_PASS'"/>

Включите автозагрузку модуля в conf/autoload_config/modules.conf.xml:

<load module="mod_nibblebill"/>

Старт и рестарт модуля:

Из консоли fs_cli выполните unload mod_nibblebill и load mod_nibblebill.

Таблицы БД

Убедитесь что в конфигурационном файле (nibblebill.conf.xml), указаны корректные значения: ODBC драйвер, база данных, таблица и ее поля.

Пример таблицы в MySQL:

mysql> use tcapi;
mysql> select * from accounts;
+--------+--------------+---------+
| id     | name         | cash    |
+--------+--------------+---------+
| 1      | Darren       | 41.4161 |
| 2      | Joe          | 50      |
| 9      | tester9      | 50      |
| 10     | tester10     | 44.8213 |
| 837269 | My Company   | 50      |
+--------+--------------+---------+
5 rows in set (0.00 sec)

  В приведенном выше примере, таблица "accounts" в принадлежит БД "tcapi". Она содержит поля "id" и "cash" для использования в сценарии биллинга.
«id» - представляет код аккаунта, а «cash» текущий баланс средств на счету аккаунта.
Конфигурационный файл nibblebill.conf.xml должен содержать соответствующие параметры:

<param name="odbc-dsn" value="mysql://db_ip/db_name"/>
<param name="db_table" value="accounts"/>
<param name="db_column_cash" value="cash"/>
<param name="db_column_account" value="id"/>
create table accounts (     
      id bigserial not null,
      name varchar(256),
      cash double precision not null 
);
CREATE TABLE accounts (
      id int NOT NULL PRIMARY KEY,
      name VARCHAR(255),
      cash double precision NOT NULL
);

Использование модуля

Это метод по умолчанию, он основывается на концепции heartbeat. Каждые Х секунд, мы списываем Y со счета аккаунта.

Для тарификации вызова, должны быть заданы как минимум две переменные в канале вызова. Это nibble_rate и nibble_account. Для модуля nibblebill не имеет значения, как инициированы переменные, в директории пользователя, в XML диалплане или в Lua скрипте.

В простейшем случае, вы можете добавить переменные в директорию пользователя:

<variable name="nibble_rate" value="0.03"/>
<variable name="nibble_account" value="18238"/>

Теперь пользователь будет обслужен по тарифу 0.03/минута за каждый звонок, входящий или исходящий.

По умолчанию, сердцебиение (heartbeat) установлено 60 секунд. Это значит, что каждые 60 секунд со счета аккаунта списывается |0.03| денежной единицы.

Обратите внимание, что все математические вычисления выполняются с использованием внутренних счетчиков микросекунд FS. А это значит, что:
  • Если heartbeat не отправлено вовремя, вы получите доли копеек ( или центов ). Обслуживающая БД должна быть готова к этому.
  • Счетчики считают время между тиками точно. Это время не останется «неучтённым».

Вы можете изменить глобальный пареметр в конфигурационном файле nibblebill.conf.xml:

<param name="global_heartbeat" value="300">

Это значит что «heartbeat» будет отправлен каждые 300 секунд или раз в 5 минут. Отсчет может быть и ежесекундный, но это не очень правильно, мягко говоря.

Вы можете установить требуемый отсчет в секундах используя переменную nibble_increments.
Логика следующая:

if time < increment {
    billing = increment 
} else { 
    billing = ceil(time/increment) * increment
}

Чтобы установить прирост биллинга в 30 секунд, задайте переменную как:

<variable name="nibble_increment" value="30" />

Возможно использование модуля без использования «heartbeat». Это означает что тарификация будет произведена по окончании вызова. Вы должны определить дополнительную переменную в файле nibblebill.conf.xml.

<param name="global_heartbeat" value="off">

Если это сделано, тарификация будет выполнена после hangup. Вычисление времени будет произведено с момента ответа на вызов до его окончания. Если вызов не был отвечен, тарификация не считается.

Вычисления производятся по следующей формуле:

[time_call_ended] - [time_call_answered] x [rate_per_minute] = total_bill_rate

Данный метод не позволяет котролировать звонок непосредственно в процессе вызова, а значит возможно мошенничество и превышение лимитов со стороны пользователей.

Примеры

Возможно назначать разные тарифы для каждого пользователя.

Пусть будут два пользователя, один с тарифом /0.05/минута, а другой 0.10/минута. Или направление на номер |800|.
Сперва задайте в директории пользователей:

<user id="dschreiber">
    <params>
        <param name="password" value="1234"/>
    </params>
    <variables>
        <variable name="nibble_rate" value="0.05"/>
        <variable name="nibble_account" value="8182"/>
        <variable name="default_areacode" value="415"/>
        <variable name="toll_allow" value="domestic,international,local"/>
        <variable name="user_context" value="default"/>
    </variables>
</user>
<user id="expensive_guy">
    <params>
        <param name="password" value="1234"/>
    </params>
    <variables>
        <variable name="nibble_rate" value="0.10"/>
        <variable name="nibble_account" value="2932"/>
        <variable name="default_areacode" value="212"/>
        <variable name="toll_allow" value="domestic,international,local"/>
        <variable name="user_context" value="default"/>
    </variables>
</user>

Тогда в диалплане, надо назначить только бесплатное направление:

<extension name="tollfree800">
    <condition field="destination_number" expression="^8(800\d{7})$">
        <action application="set" data="nibble_rate=0"/>
        <action application="bridge" data="sofia/gateway/bandwidth.com/$1"/>
    </condition>
</extension>

Все вызовы кроме «tollfree800», будут тарифицированы по цене назначенной в аккаунте пользователей.

пример настроек аккаунта пользователя Directory:

 <include>
     <user id="diegoviola">
         <params>
             <param name="password" value="1234"/>
         </params>
         <variables>
             <variable name="toll_allow" value="domestic,international,local"/>
             <variable name="user_context" value="default"/>
             <variable name="nibble_account" value="1"/>
         </variables>
     </user>
 </include>
 
 
 <include>
     <user id="dschreiber">
         <params>
             <param name="password" value="1234"/>
         </params>
         <variables>
             <variable name="toll_allow" value="domestic,international,local"/>
             <variable name="user_context" value="default"/>
             <variable name="nibble_account" value="2"/>
         </variables>
     </user>
 </include>
 

Расширение диалплана, которое вы хотите тарифицировать:

 <extension name="outbound">
     <condition field="destination_number" expression="^9(\d{10,})$">
         <action application="set" data="nibble_rate=0.05"/>
         <action application="set" data="nibble_account=${user_data(${caller_id_number}@${domain_name} var nibble_account)}"/>
         <action application="bridge" data="sofia/gateway/teliax/$1"/>
     </condition>
 </extension>
 

В примере все вызовы тарифицируются по 0.05/минута, за исключением звонков на телефонный код 919, для которых тариф по 0.07/минута и вызовов на номер 800, которые являются бесплатными.
Значение nibble_account получается динамически из директории пользователя, методом api - user_data.
В данном примере nibble_rate назначается в диалплане, в зависимости от направления. Это значение имеет приоритет, над переменной заданной в директории пользователя.

 <extension name="tollfree800">
     <condition field="destination_number" expression="^1{0,1}(800\d{7})$">
         <action application="set" data="nibble_account=${user_data(${caller_id_number}@${domain_name} var nibble_account)}"/>
         <action application="set" data="nibble_rate=0"/>
         <action application="bridge" data="sofia/gateway/bandwidth.com/$1"/>
     </condition>
 </extension>
 
 
 <extension name="special919rate">
     <condition field="destination_number" expression="^1{0,1}(919\d{7})$">
         <action application="set" data="nibble_account=${user_data(${caller_id_number}@${domain_name} var nibble_account)}"/>
         <action application="set" data="nibble_rate=0.07"/>
         <action application="bridge" data="sofia/gateway/bandwidth.com/$1"/>
     </condition>
 </extension>
 
 
 <extension name="domestic">
     <condition field="destination_number" expression="^(1{0,1}\d{10})$">
         <action application="set" data="nibble_account=${user_data(${caller_id_number}@${domain_name} var nibble_account)}"/>
         <action application="set" data="nibble_rate=0.05"/>
         <action application="bridge" data="sofia/gateway/bandwidth.com/$1"/>
     </condition>
 </extension>
 

Идея заключается в том, что можно менять тариф прямо во время вызова,

вызвав метод flush приложения nibblebill, можно сбросить в БД сумму аккумулированную вызовом по старому тарифу и перейти на новый тариф.

Вызывающий абонент в первой части вызвова тарифицируется по 1.00/минута пока разговаривает с первым уровнем поддержки, если же понадобится поговорить со вторым уровнем, тариф вырастет до 5.00/минута. Тариф можно установить и «0», например, пока вызов находится на удержании или ожидает в очереди.

 <extension name="tier1">
     <condition field="destination_number" expression="^2001$">
         <!-- Save anything billed at a previous rate -->
         <action application="nibblebill" data="flush"/>
         <!-- Change the rate -->
         <action application="set" data="nibble_rate=1.00"/>
         <!-- Transfer to Tier1 rep -->
         <action application="transfer" data="1001 XML default"/>
     </condition>
 </extension>
 
 <extension name="tier2">
     <condition field="destination_number" expression="^2002$">
         <!-- Save anything billed at a previous rate -->
         <action application="nibblebill" data="flush"/>
         <!-- Change the rate -->
         <action application="set" data="nibble_rate=5.00"/>
         <!-- Transfer to Tier2 rep -->
         <action application="transfer" data="1002 XML default"/>
     </condition>
 </extension>
 

Другой возможный сценарий - платный тариф для разговора с поддержкой, а затем переход на бесплатный тариф, для прохождения телефонного опроса:

 <extension name="survey-after-call">
     <condition field="destination_number" expression="^2000$">
         <!-- Handle support request here at $1.00/minute via extension 1001 -->
         <action application="set" data="nibble_rate=1.00"/>
         <action application="set" data="hangup_after_bridge=false"/> 
         <action application="bridge" data="sofia/internal/1001@$${domain}"/>
         <action application="nibblebill" data="flush"/>
         <!-- Set rate to 0, then transfer caller to the survey IVR -->
         <action application="set" data="nibble_rate=0.00"/>
         <action application="bridge" data="sofia/internal/1002@$${domain}"/>
     </condition>
 </extension>
 

Когда баланс падает до значения заданного в конфигурации в nobal_amt, вызов переадресуется на расширение назначеное в параметре nobal_action.

conf/autoload_configs/nibblebill.conf.xml:

 <!-- By default, terminate a caller when their balance hits $0.00. You can set this to a negative number. -->
 <param name="nobal_amt" value="0"/>
 <param name="nobal_action" value="hangup XML default"/>
 

В примере ниже nobal_action имеет значение «hangup XML default». Это говорит mod_nibblebill перенаправить вызов на расширение hangup в контексте default вашего XML диалплана, когда баланс достигает значения заданного в nobal_amt. Перед разъединением будет проиграно сообщение.

 <extension name="hangup">
     <condition field="destination_number" expression="^(hangup)$">
         <action application="playback" data="no_more_funds.wav"/>
         <action application="hangup"/>
     </condition>
 </extension>
 

API Commands

Следующие команды могут быть использованы в диалплане, CLI или API. Синтаксис в основном одинаковый, за исключением очевидных различий в формате команд:

Диалплан:

 <action application="nibblebill" data="action [params]">
 

CLI или API:

 nibblebill <channel-uuid> <action> [params]
 

Используйте в диалплане просто, или в CLI c UUID вызова, чтобы получить баланс на данный момент. Не сохраненные на данный момент в БД значения не учитываются.

 <action application="nibblebill" data="check"/>
 

Используйте в диалплане:

 <action application="nibblebill" data="flush"/>
 

…чтобы немедленно записать в базу данных текущее значение биллинга. Тарификация может продолжаться, но все что было получено до сих пор, будет вычислено и сохранено.

Этот метод нельзя применить, если тарификация была поставлена на паузу.

Используйте в диалплане:

 <action application="nibblebill" data="pause"/>
 

…поставит тарификацию на паузу. Если вызов окончен, пока тарификация на паузе, будет вычислено значения полученые до постановки вызова на паузу, время проведенное в паузе не учитывается, но тарификацию до паузы надо еще записать с помощью команды resume.

Если вызвать паузу для вызова уже поставленого на паузу, ничего не произойдет.

Используйте в диалплане:

 <action application="nibblebill" data="resume"/>
 

…возвращает вызов поставленный на паузу в биллинг. Время между паузой и резюме не тарифицируется. Команды могут быть вызваны множество раз на протяжении вызова и периоды между между паузами будут тарифицированы.

Используйте в диалплане:

 <action application="nibblebill" data="reset"/>
 

… сбросит таймер биллинга на текущее время. Время вызова прошедшее до сброса, будет считаться нетарифицируемым и "свободным". (если оно не было до этого отравлено в хранилище, например, при помощи "flush").

Используйте в диалплане:

 <action application="nibblebill" data="adjust 5.00"/>
 

…добавить или списать определенную сумму со счета аккаунта (в примере 5.00). Это происходит немедленно и ваша ответственность обеспечить доступность базы данных на этот момент, чтобы списание или зачисление прошло успешно.

Используйте отрицательное значение для списания средств.

Включить heartbeat прямо во время вызова:

 <action application="nibblebill" data="heartbeat 60"/>
 

Установить значние heartbeat только для этого вызова. Таким образом вы можете назначать уникальный heartbeat для разных направлений.

Если вы хотите тарифицировать только B-leg вызова, переменная enable_heartbeat_evetns должна быть задана:

 <action application="bridge" data="{enable_heartbeat_events=5,nibble_rate=1,nibble_account=0838833133}sofia/external/$1@tel.co.th"/>
 

Дополнительно

По завершению вызова, модуль устанавливает переменную nibble_total_billed. Вы можете использовать ее для записи данных в CDR.

Когда включен режим bypass_media, нельзя установить heartbeat чаще 60 секунд.

  • Q: Возможно производить тарификацию только для вызываемой стороны - B-Leg, но не для вызывающей - A-Leg?

    • Да, возможно. Требуется установить переменные биллинга только для вызываемой стороны, но не для вызывающей. Вы можете, также, включить heartbeat на вызываемой стороне, но это не нужно - если заданы переменные счет будет выставлен автоматически в конце вызова.

  • Q: Можно ли тарифицировать множественные вызовы для исходящей стороны = B-Leg, например при вызове - bridge нескольких абонентов?

  • Q: Когда стартует тарификация?

    • Биллинг начинается с того момента, когда вызов отвечен стороной А (A-leg). Это может вызвать некоторые проблемы, если неправильно обрабатывается early media и т.д., и FreeSWITCH считает, что на вызов ответили, даже если другая сторона еще не подняла трубку.

  • Q: Возможно ли тарифицировать A-Leg и B-leg по разным тарифам и аккаунтам?

    • Да, можно. Вот пример диалплана.

       <extension name="Internal-XXX_Mobile">
           <condition field="destination_number" expression="^(1\d+)$">
               <action application="set" data="hangup_after_bridge=true"/>
               <action application="set" data="nibble_account=9999"/>
               <action application="set" data="nibble_rate=0.05"/>
               <action application="export" data="nolocal:nibble_account=1111"/>
               <action application="export" data="nolocal:nibble_rate=0.03"/>
               <action application="bridge" data="sofia/external/$1@10.0.0.10"/>
               <action application="hangup"/>
           </condition>
       </extension> 
       

A-Leg тарифицируется переменными заданными при помощи «set», а B-Leg тарифицируется данными переменных заданными при помощи «export».

Если в CDR данные сохраняются раздельно по AB сторонам, то это можно использовать для сбора дополнительной информации о тарификации.

 

 

  • freeswitch/mod/mod_nibblebill.txt
  • Последние изменения: 2020/04/23