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) Общая сумма которая будет списана со счета после завершения звонка.
Сценарии использования
Дебет (предоплата/Pre-paid)
Позволяет производить вызовы, пока внесенная на аккаунт сумма не исчерпана полностью.
Когда деньги подходят к концу проигрываеся тон или голосовое сообщение.
Когда деньги заканчиваются полностью, вызов может быть перенаправлен на указанное расширение или просто разъединен.
Кредит (постоплата/Post-pay)
Если поле БД позволяет, вы можете разрешить пользователям уходить в глубокий минус. Оплата в данном случае может производится уже после использования.
Однако можно защититься от злоупотреблений указав нижнее значение кредита для аккаунта (например задать нижний порог списания средств в месяц).
Оплата за звонок (Pay-Per-Call)
Вы можете предоставлять услуги по требованию с оплатой во время вызова, фиксированной суммой или поминутно, после ввода, например, номера кредитной карты.
Максимальный кредит и/или защита от мошенничества (фрода)
Вы можете настроить поле кредита, которое истощается пользователями, по принципу предоплаты выше. Когда они используют все свои кредитные средства за один день/неделю/месяц/и т. д. и не смогут больше совершать вызовы, можно будет увеличить кредит на счету на определенный период.
Что-то вроде «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"/> |
Создание таблицы PostgreSQL
create table accounts ( id bigserial not null, name varchar(256), cash double precision not null ); |
Создание таблицы MySQL
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> |
Тарифы направлений (DIDs)
В примере все вызовы тарифицируются по 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] |
Check
Используйте в диалплане просто, или в CLI c UUID вызова, чтобы получить баланс на данный момент. Не сохраненные на данный момент в БД значения не учитываются.
<action application="nibblebill" data="check"/>
|
Flush
Используйте в диалплане:
<action application="nibblebill" data="flush"/>
|
…чтобы немедленно записать в базу данных текущее значение биллинга. Тарификация может продолжаться, но все что было получено до сих пор, будет вычислено и сохранено.
Этот метод нельзя применить, если тарификация была поставлена на паузу.
Pause
Используйте в диалплане:
<action application="nibblebill" data="pause"/>
|
…поставит тарификацию на паузу. Если вызов окончен, пока тарификация на паузе, будет вычислено значения полученые до постановки вызова на паузу, время проведенное в паузе не учитывается, но тарификацию до паузы надо еще записать с помощью команды resume.
Если вызвать паузу для вызова уже поставленого на паузу, ничего не произойдет.
Resume
Используйте в диалплане:
<action application="nibblebill" data="resume"/>
|
…возвращает вызов поставленный на паузу в биллинг. Время между паузой и резюме не тарифицируется. Команды могут быть вызваны множество раз на протяжении вызова и периоды между между паузами будут тарифицированы.
Reset
Используйте в диалплане:
<action application="nibblebill" data="reset"/>
|
… сбросит таймер биллинга на текущее время. Время вызова прошедшее до сброса, будет считаться нетарифицируемым и "свободным". (если оно не было до этого отравлено в хранилище, например, при помощи "flush").
Добавление суммы к счету
Используйте в диалплане:
<action application="nibblebill" data="adjust 5.00"/>
|
…добавить или списать определенную сумму со счета аккаунта (в примере 5.00). Это происходит немедленно и ваша ответственность обеспечить доступность базы данных на этот момент, чтобы списание или зачисление прошло успешно.
Используйте отрицательное значение для списания средств.
Включение Heartbeat в сессии
Включить heartbeat прямо во время вызова:
<action application="nibblebill" data="heartbeat 60"/>
|
Установить значние heartbeat только для этого вызова. Таким образом вы можете назначать уникальный heartbeat для разных направлений.
Тарифицировать только B leg
Если вы хотите тарифицировать только 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 секунд.
FAQs
-
Q: Возможно производить тарификацию только для вызываемой стороны - B-Leg, но не для вызывающей - A-Leg?
-
Да, возможно. Требуется установить переменные биллинга только для вызываемой стороны, но не для вызывающей. Вы можете, также, включить
heartbeat
на вызываемой стороне, но это не нужно - если заданы переменные счет будет выставлен автоматически в конце вызова.
-
-
Q: Можно ли тарифицировать множественные вызовы для исходящей стороны = B-Leg, например при вызове -
bridge
нескольких абонентов?-
Да, смотрите Bridge - variable scope.
-
-
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 сторонам, то это можно использовать для сбора дополнительной информации о тарификации.