[Freeswitch-svn] [commit] r11219 - freeswitch/trunk/scripts/contrib/dschreiber/mod_nibblebill
FreeSWITCH SVN
dschreiber at freeswitch.org
Wed Jan 14 23:56:15 PST 2009
Author: dschreiber
Date: Thu Jan 15 01:56:15 2009
New Revision: 11219
Log:
Updated nibblebill to add some dialplan applications
more to come
Modified:
freeswitch/trunk/scripts/contrib/dschreiber/mod_nibblebill/mod_nibblebill.c
Modified: freeswitch/trunk/scripts/contrib/dschreiber/mod_nibblebill/mod_nibblebill.c
==============================================================================
--- freeswitch/trunk/scripts/contrib/dschreiber/mod_nibblebill/mod_nibblebill.c (original)
+++ freeswitch/trunk/scripts/contrib/dschreiber/mod_nibblebill/mod_nibblebill.c Thu Jan 15 01:56:15 2009
@@ -29,9 +29,15 @@
* Darren Schreiber <d at d-man.org>
*
* mod_nibblebill.c - Nibble Billing
- * Set the session heartbeat on a call that has a billrate and deduct currency from
- * a database table each "tick" of the billing until funds are depleted.
- * Terminate the call upon depletion.
+ * Purpose is to allow real-time debiting of credit or cash from a database while calls are in progress. I had the following goals:
+ *
+ * Debit credit/cash from accounts real-time
+ * Allow for billing at different rates during a single call
+ * Allow for warning callers when their balance is low (via audio, in-channel)
+ * Allow for disconnecting or re-routing calls when balance is depleted
+ * Allow for billing to function as listed above with multiple concurrent calls
+ *
+ * Thanks go to bandwidth.com for funding this work.
*
*
* TODO: Create an override flag so you can force a start-time for when billing should count *from*
@@ -52,13 +58,20 @@
#endif
/* Defaults */
-/* static char SQL_LOOKUP[] = "SELECT cash FROM accounts WHERE id=\"%s\""; */
-static char SQL_SAVE[] = "UPDATE accounts SET cash=cash-%f WHERE id=\"%s\"";
+#ifdef SWITCH_HAVE_ODBC
+/*static char SQL_LOOKUP[] = "SELECT %s FROM %s WHERE %s=\"%s\"";*/
+static char SQL_SAVE[] = "UPDATE %s SET %s=%s-%f WHERE %s=\"%s\"";
+#endif
typedef struct
{
switch_time_t lastts; /* Last time we did any billing */
float total; /* Total amount billed so far */
+
+ switch_time_t pausets; /* Timestamp of when a pause action started. 0 if not paused */
+ float bill_adjustments; /* Adjustments to make to the next billing, based on pause/resume events */
+
+ float manual_adjustments; /* Manually added/deducted amounts from this account, to be committed at next database query */
} nibble_data_t;
@@ -88,12 +101,16 @@
float nobal_amt; /* Minimum amount that must remain in the account */
char *nobal_action; /* Drop action */
+ /* Other options */
+ int global_heartbeat; /* Supervise and bill every X seconds, 0 means off */
+
/* Database settings */
char *db_username;
char *db_password;
char *db_dsn;
- char *lookup_query;
- char *save_query;
+ char *db_table;
+ char *db_column_cash;
+ char *db_column_account;
#ifdef SWITCH_HAVE_ODBC
switch_odbc_handle_t *master_odbc;
#else
@@ -117,12 +134,14 @@
SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_username, globals.db_username);
SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_password, globals.db_password);
SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_dsn, globals.db_dsn);
-SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_lookup_query, globals.lookup_query);
-SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_save_query, globals.save_query);
+SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_table, globals.db_table);
+SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_column_cash, globals.db_column_cash);
+SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_db_column_account, globals.db_column_account);
SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_percall_action, globals.percall_action);
SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_lowbal_action, globals.lowbal_action);
SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_nobal_action, globals.nobal_action);
+#ifdef SWITCH_HAVE_ODBC
static int nibblebill_callback(void *pArg, int argc, char **argv, char **columnNames)
{
nibblebill_results_t *cbt = (nibblebill_results_t *) pArg;
@@ -131,6 +150,7 @@
return 0;
}
+#endif
static switch_status_t load_config(void)
{
@@ -141,7 +161,7 @@
if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", cf);
status = SWITCH_STATUS_SUCCESS; /* We don't fail because we can still write to a text file or buffer */
- goto done;
+ goto setdefaults;
}
if ((settings = switch_xml_child(cfg, "settings"))) {
@@ -149,35 +169,46 @@
char *var = (char *) switch_xml_attr_soft(param, "name");
char *val = (char *) switch_xml_attr_soft(param, "value");
- if (!strcasecmp(var, "db-username")) {
+ if (!strcasecmp(var, "db_username")) {
set_global_db_username(val);
- } else if (!strcasecmp(var, "db-password")) {
+ } else if (!strcasecmp(var, "db_password")) {
set_global_db_password(val);
- } else if (!strcasecmp(var, "db-dsn")) {
+ } else if (!strcasecmp(var, "db_dsn")) {
set_global_db_dsn(val);
- } else if (!strcasecmp(var, "lookup_query")) {
- set_global_lookup_query(val);
- } else if (!strcasecmp(var, "save_query")) {
- set_global_save_query(val);
+ } else if (!strcasecmp(var, "db_table")) {
+ set_global_db_table(val);
+ } else if (!strcasecmp(var, "db_column_cash")) {
+ set_global_db_column_cash(val);
+ } else if (!strcasecmp(var, "db_column_account")) {
+ set_global_db_column_account(val);
} else if (!strcasecmp(var, "percall_action")) {
set_global_percall_action(val);
+ } else if (!strcasecmp(var, "percall_max_amt")) {
+ globals.percall_max_amt = atof(val);
} else if (!strcasecmp(var, "lowbal_action")) {
set_global_lowbal_action(val);
+ } else if (!strcasecmp(var, "lowbal_amt")) {
+ globals.lowbal_amt = atof(val);
} else if (!strcasecmp(var, "nobal_action")) {
set_global_nobal_action(val);
+ } else if (!strcasecmp(var, "nobal_amt")) {
+ globals.nobal_amt = atof(val);
+ } else if (!strcasecmp(var, "global_heartbeat")) {
+ globals.global_heartbeat = atoi(val);
}
}
}
-done:
+/* Set defaults for any variables still not set */
+setdefaults:
if (switch_strlen_zero(globals.db_username)) {
- set_global_db_username("phonebooth");
+ set_global_db_username("bandwidth.com");
}
if (switch_strlen_zero(globals.db_password)) {
set_global_db_password("dev");
}
if (switch_strlen_zero(globals.db_dsn)) {
- set_global_db_dsn("phonebooth");
+ set_global_db_dsn("bandwidth.com");
}
if (switch_strlen_zero(globals.percall_action)) {
set_global_percall_action("hangup");
@@ -188,14 +219,13 @@
if (switch_strlen_zero(globals.nobal_action)) {
set_global_nobal_action("hangup");
}
- /* Need to add defaults for the integers! */
#ifdef SWITCH_HAVE_ODBC
if (globals.db_dsn) {
if (!(globals.master_odbc = switch_odbc_handle_new(globals.db_dsn, globals.db_username, globals.db_password))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot create handle to ODBC Database!\n");
status = SWITCH_STATUS_FALSE;
- goto reallydone;
+ goto done;
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Opened ODBC Database handle!\n");
}
@@ -203,7 +233,7 @@
if (switch_odbc_handle_connect(globals.master_odbc) != SWITCH_ODBC_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot connect to ODBC driver/database %s (user: %s / pass %s)!\n", globals.db_dsn, globals.db_username, globals.db_password);
status = SWITCH_STATUS_FALSE;
- goto reallydone;
+ goto done;
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Opened ODBC Database!\n");
}
@@ -216,7 +246,7 @@
#endif
#ifdef SWITCH_HAVE_ODBC
-reallydone:
+done:
#endif
if (xml) {
@@ -245,7 +275,11 @@
static switch_status_t sched_billing(switch_core_session_t *session)
{
- switch_core_session_enable_heartbeat(session, 60);
+ if (globals.global_heartbeat > 0) {
+ switch_core_session_enable_heartbeat(session, globals.global_heartbeat);
+ }
+
+ // Check account balance here
return SWITCH_STATUS_SUCCESS;
@@ -263,7 +297,9 @@
nibble_data_t *nibble_data;
switch_time_t ts = switch_timestamp_now();
float billamount;
+ float adjustments = 0;
char date[80] = "";
+ char *tmp;
char *uuid;
switch_size_t retsize;
switch_time_exp_t tm;
@@ -313,6 +349,15 @@
/* Get our nibble data var. This will be NULL if it's our first call here for this session */
nibble_data = (nibble_data_t *) switch_channel_get_private(channel, "_nibble_data_");
+ /* Are we in paused mode? If so, we don't do anything here - go back! */
+ if (nibble_data->pausets > 0) {
+ if (globals.mutex) {
+ switch_mutex_unlock(globals.mutex);
+ }
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received heartbeat, but we're paused - ignoring\n");
+ return SWITCH_STATUS_SUCCESS;
+ }
+
/* Have we done any billing on this channel yet? If no, set up vars for doing so */
if (!nibble_data) {
nibble_data = switch_core_session_alloc(session, sizeof(*nibble_data));
@@ -337,34 +382,46 @@
if ((ts - nibble_data->lastts) > 0) {
/* Convert billrate into microseconds and multiply by # of microseconds that have passed */
billamount = (atof(billrate) / 1000000 / 60) * ((ts - nibble_data->lastts));
+ adjustments = nibble_data->manual_adjustments + nibble_data->bill_adjustments;
/* if ODBC call fails, we should return BEFORE updating the timestamp of last success! */
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Billing $%f to %s (Call: %s / %f so far)\n", billamount, billaccount, uuid, nibble_data->total);
+ if (adjustments > 0) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "(Also adjusting the account by $%f)\n", adjustments);
+ }
+
/* DO ODBC BILLING HERE! */
#ifdef SWITCH_HAVE_ODBC
char sql[1024] = "";
nibblebill_results_t pdata;
memset(&pdata, 0, sizeof(pdata));
- if (!globals.save_query){
- snprintf(sql, 1024, SQL_SAVE, billamount, billaccount);
- } else {
- snprintf(sql, 1024, globals.save_query, billamount, billaccount);
- }
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Doing static update query\n[%s]\n", sql);
+ snprintf(sql, 1024, SQL_SAVE, globals.db_table, globals.db_column_cash, globals.db_column_cash, (billamount - adjustments), globals.db_column_account, billaccount);
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Doing update query\n[%s]\n", sql);
if (!(switch_odbc_handle_callback_exec(globals.master_odbc, sql, nibblebill_callback, &pdata) == SWITCH_ODBC_SUCCESS)){
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "DB Error while updating cash!\n");
/* TODO: If this is a hangup event, we should store this billing in a text file while the DB is unavailable */
- }
+ } else {
#endif
+ /* Increment total cost */
+ nibble_data->total += billamount;
- /* Increment total cost */
- nibble_data->total += billamount;
+ /* Get rid of any manual adjustments that were just committed */
+ nibble_data->manual_adjustments = 0;
+ nibble_data->bill_adjustments = 0;
+
+ /* Update channel variable with current billing */
+ tmp = switch_mprintf("%f", nibble_data->total);
+ switch_channel_set_variable(channel, "nibble_total_billed", tmp);
+ switch_safe_free(tmp);
+#ifdef SWITCH_HAVE_ODBC
+ }
+#endif
} else {
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Just tried to bill %s negative minutes! wtf?\n", uuid);
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Just tried to bill %s negative minutes! wtf? That should be impossible.\n", uuid);
}
/* Update the last time we billed */
@@ -373,14 +430,16 @@
/* Save everything back */
switch_channel_set_private(channel, "_nibble_data_", nibble_data);
- /* Release the session lock */
- switch_core_session_rwunlock(session);
-
/* Done changing - release lock */
if (globals.mutex) {
switch_mutex_unlock(globals.mutex);
}
+ /* Go check if this call is allowed to continue */
+
+ /* Release the session lock */
+ switch_core_session_rwunlock(session);
+
return SWITCH_STATUS_SUCCESS;
}
@@ -421,28 +480,99 @@
switch_core_session_rwunlock(session);
}
-SWITCH_STANDARD_APP(nibblebill_app_function)
+
+static float nibblebill_check(switch_core_session_t *session)
{
switch_channel_t *channel = switch_core_session_get_channel(session);
+ nibble_data_t *nibble_data;
+ float amount = 0;
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "We made it!\n");
+ if (!channel) {
+ return -99999;
+ }
+
+ /* Lock this session's data for this module while we tinker with it */
+ if (globals.mutex) {
+ switch_mutex_lock(globals.mutex);
+ }
+
+ /* Get our nibble data var. This will be NULL if it's our first call here for this session */
+ nibble_data = (nibble_data_t *) switch_channel_get_private(channel, "_nibble_data_");
+
+ amount = nibble_data->total;
+
+ /* Done checking - release lock */
+ if (globals.mutex) {
+ switch_mutex_unlock(globals.mutex);
+ }
+
+ return amount;
+}
+
+static void nibblebill_adjust(switch_core_session_t *session, float amount)
+{
+ switch_channel_t *channel = switch_core_session_get_channel(session);
+ nibble_data_t *nibble_data;
+ char *uuid;
if (!channel) {
return;
}
+
+ uuid = switch_core_session_get_uuid(session);
+
+ /* Lock this session's data for this module while we tinker with it */
+ if (globals.mutex) {
+ switch_mutex_lock(globals.mutex);
+ }
+
+ /* Get our nibble data var. This will be NULL if it's our first call here for this session */
+ nibble_data = (nibble_data_t *) switch_channel_get_private(channel, "_nibble_data_");
+
+ /*Add or remove amount from adjusted billing here */
+ nibble_data->manual_adjustments += amount;
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Current adjustment for %s is $%f\n", uuid, amount);
+
+ /* Done changing - release lock */
+ if (globals.mutex) {
+ switch_mutex_unlock(globals.mutex);
+ }
+
+ /* Release the session lock */
+ switch_core_session_rwunlock(session);
+}
+
+#define CHECK_APP_SYNTAX ""
+SWITCH_STANDARD_APP(nibblebill_check_app_function)
+{
+ float amount;
+
+ amount = nibblebill_check(session);
+
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Current billing is at $%f\n", amount);
+}
+
+#define ADJUST_APP_SYNTAX "<amount>"
+SWITCH_STANDARD_APP(nibblebill_adjust_app_function)
+{
+ char *mydata = NULL;
+ int argc = 0;
+ char *argv[4] = { 0 };
+ float amount;
-/* if (!(mydata = switch_core_session_strdup(session, data))) {
+ if (!(mydata = switch_core_session_strdup(session, data))) {
return;
}
if ((argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) {
- destnum = argv[0];
- do_billing(session);
- }*/
+ amount = atof(argv[0]);
+ nibblebill_adjust(session, amount);
+ }
}
/* We get here from the API only (theoretically) */
-SWITCH_STANDARD_API(nibblebill_function)
+#define CHECK_API_SYNTAX "<uuid>"
+SWITCH_STANDARD_API(nibblebill_check_api_function)
{
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "We made it!\n");
@@ -459,7 +589,7 @@
{
/* on_init */ NULL,
/* on_routing */ NULL, /* Need to add a check here for anything in their account before routing */
- /* on_execute */ sched_billing, /* Turn on heartbeat for this session */
+ /* on_execute */ sched_billing, /* Turn on heartbeat for this session and do an initial account check */
/* on_hangup */ do_billing, /* On hangup - most important place to go bill */
/* on_exch_media */ NULL,
/* on_soft_exec */ NULL,
@@ -479,8 +609,20 @@
/* connect my internal structure to the blank pointer passed to me */
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
- SWITCH_ADD_API(api_interface, "check", "Check the balance of an account", nibblebill_function, "");
- SWITCH_ADD_APP(app_interface, "check", "Check the balance on an account", "Perform an account check for fund balance", nibblebill_app_function, "<number>", SAF_SUPPORT_NOMEDIA);
+
+ /* Add API and CLI commands */
+/* SWITCH_ADD_API(api_interface, "pause", "Pause billing on a session", nibblebill_pause_api_function, PAUSE_API_SYNTAX);
+ SWITCH_ADD_API(api_interface, "resume", "Resume billing on a session", nibblebill_resume_api_function, RESUME_API_SYNTAX);
+ SWITCH_ADD_API(api_interface, "reset", "Reset billing start time to right now - disregard any unbilled time thus far", nibblebill_reset_api_function, RESET_API_SYNTAX);
+ SWITCH_ADD_API(api_interface, "adjust", "Add or deduct a dollar amount from a caller's account", nibblebill_adjust_api_function, ADJUST_API_SYNTAX);*/
+ SWITCH_ADD_API(api_interface, "check", "Check the balance of a session", nibblebill_check_api_function, CHECK_API_SYNTAX);
+
+ /* Add dialplan applications */
+/* SWITCH_ADD_APP(app_interface, "pause", "Pause billing on a session", "Pause billing on a session", nibblebill_pause_app_function, PAUSE_APP_SYNTAX, SAF_NONE);
+ SWITCH_ADD_APP(app_interface, "resume", "Resume billing on a session", "Resume billing on a session", nibblebill_resume_app_function, RESUME_APP_SYNTAX, SAF_NONE);
+ SWITCH_ADD_APP(app_interface, "reset", "Check the balance of a session", "Check the balance of a session", nibblebill_reset_app_function, RESET_APP_SYNTAX, SAF_NONE);*/
+ SWITCH_ADD_APP(app_interface, "adjust", "Add or deduct a dollar amount from a caller's account", "Add or deduct a dollar amount from a callers' account (use negative numbers to deduct)", nibblebill_adjust_app_function, ADJUST_APP_SYNTAX, SAF_NONE);
+ SWITCH_ADD_APP(app_interface, "check", "Check the balance on an account", "Perform an account check for fund balance on a specific caller's account", nibblebill_check_app_function, CHECK_APP_SYNTAX, SAF_NONE);
/* register state handlers for billing */
switch_core_add_state_handler(&nibble_state_handler);
More information about the Freeswitch-svn
mailing list