Support A2Billing :

provided by Star2Billing S.L.

Support A2Billing :
It is currently Thu Mar 28, 2024 9:35 pm
VoIP Billing solution


All times are UTC




Post new topic Reply to topic  [ 41 posts ]  Go to page Previous  1, 2, 3
Author Message
 Post subject:
PostPosted: Fri Jul 13, 2007 1:29 pm 
Offline
Moderator
User avatar

Joined: Tue Jun 06, 2006 12:14 pm
Posts: 685
Location: florida
meessras wrote:
    The "RINGING" time of the second leg was not taken into consideration when calculating the timeout. As a result, credit for prepaid user can still go negative. If the billing block is big (e.g 60 seconds) then this problem may not appear.


That's why before I suggested in calculating the timeout to take the first leg and add 1 minute to it. I'd think that should cover you 99% of the time, and if you wait a max of 60 seconds before failing on the 2nd leg, it covers you 100%.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 13, 2007 2:29 pm 
Offline
User avatar

Joined: Mon Apr 30, 2007 6:43 am
Posts: 1060
Location: Canada
If we assume that it is 1 minute, then what if someone manage to keep the 1st leg for more than 1 minutes? And what if the billing block is less than 1 minutes (30 sec, 10 sec, ...). The person will be charge the equivalent of a whole 1 minute instead of 10 seconds for example. By estimating stuff instead of taking the time to solve it once and for all, we will penalize this software, ourselves (sometimes) and the clients.

Also, as a customer, I never tolerate a company when they take something from me. A phone company was rounding my bills amounts the wrong way. They were basically rounding them "down". This was costing me 1¢ a month. I asked them to correct the situation and they said that it was only 1¢ and that it was not a big deal. Then I replied with "I you are so convinced that it's not such a big deal, then why won't you just give me my 1¢ back". I had to threaten them to get my money back for the whole year, and I will remind them to give me the same 12¢ credit every year. Here is how there were rounding their money:

2.459 -----> 2.45 instead of 1.45 (WRONG)
2.451 -----> 2.45 which is the way to do it
2.454 -----> 2.45 which is the way to do it

As you can see, they were just stripping off the unwanted decimal.

In some cases, this could have cost me most that 1¢. In fact, it could have been 1¢ per subtotal.

The solution I have given in my previous post really gives the exact time for the 1st leg. I really want to do everything possible to avoid guessing numbers, specially when it come to money.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 13, 2007 2:44 pm 
Offline
Moderator
User avatar

Joined: Tue Jun 06, 2006 12:14 pm
Posts: 685
Location: florida
asiby wrote:
If we assume that it is 1 minute, then what if someone manage to keep the 1st leg for more than 1 minutes? And what if the billing block is less than 1 minutes (30 sec, 10 sec, ...). The person will be charge the equivalent of a whole 1 minute instead of 10 seconds for example. By estimating stuff instead of taking the time to solve it once and for all, we will penalize this software, ourselves (sometimes) and the clients.


Not talking about the billing ... talking about the timeout - which is not truly known until the connection is made, but since you are already in the first call, the best you can do is estimate the true timeout. I think assuming 1 minute is pretty accurate from things I've seen. If they connect in 10 seconds, and your billing block is 10 seconds, then perhaps they don't get to talk for quite as long as they wanted, but what it accomplishes is #1 - you don't lose money and #2 - caller is billed the proper amount of money. right??


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 13, 2007 2:54 pm 
Offline
User avatar

Joined: Mon Apr 30, 2007 6:43 am
Posts: 1060
Location: Canada
I afraid that I am not convinced. Time is money. Even I they still have the credit that they expect to have right before making a call, if we only allow them to call for the wrong amount of time (less time sometimes), it will be as bad as taking some money out of they account before they check their credit. It's like those calling cards that tells you that you have 30 minutes to calls, but the call will really be disconnected after 17 minutes. I know it's an extreme case, but I have seen it many times. And I know that it in this case it is just 1 minute, but still, it's not the real time, it's a wild guess. Well, mayb not that wild, but it's a guess. And like I said before, determining the time spent on the 1st leg is done now with pin point accuracy. Why should we give that up. That part of the problem is solved now. We should be discussing about the problem that messras is having with the "free time to call" and the package deals.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 13, 2007 3:10 pm 
Offline
Moderator
User avatar

Joined: Tue Jun 06, 2006 12:14 pm
Posts: 685
Location: florida
Asiby, the problem as I see it to do any better and get the precision you desire is to have REAL-TIME billing. The timeout parameter is defined BEFORE the answer - so thus it is impossible to say what it will be. If the timeout is defined AFTER the connect signal, then I'd agree with you, but its defined BEFORE.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 13, 2007 3:43 pm 
Offline
User avatar

Joined: Mon Apr 30, 2007 6:43 am
Posts: 1060
Location: Canada
a2billing is executed twice during the cid-callback.

The 1st execution happens when you call and a2billing identify you hand hangs up. Right?

The second execution occurs when asterisk originate the callback scheduled during the 1st phase but only as soon as you pick up the phone. It's only then that asterisk will complete the originate by connecting the 1st leg to an asterisk CLI application or to a context+extention+priority.

In our case, we are almost all using a context that call a2billing.php again (the 2nd execution), but this time in callback mode.

So, the only unexpected and "hard to determine delay" here will be the time between the instant the called party picks up the phone, and the time when asterisk execute the context and launch a2billing.

People have to know that if they have something like this:

Code:
[a2billing-cid-callback-2nd-phase]
exten => _X.,1,Answer()
exten => _X.,n,Wait(2)
exten => _X.,n,DeadAGI(a2billing.php|1|callback)
exten => _X.,n,Wait(2)
exten => _X.,n,Hangup


The line exten => _X.,n,Wait(2) will induce an additional 2 seconds delay that will offset the real value of $G_startime. If you are doing a per second billing, then, you will have to take that into consideration.

But if you just have

Code:
[a2billing-cid-callback-2nd-phase]
exten => _X.,1,Answer()
exten => _X.,n,DeadAGI(a2billing.php|1|callback)
exten => _X.,n,Wait(2)
exten => _X.,n,Hangup


Then we will be talking about a few milliseconds delay unless the server is too busy and hangs in there before being able to proceed.

I hope that this will help you understand the situation better


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 19, 2007 4:40 pm 
Offline

Joined: Mon Jul 02, 2007 4:54 am
Posts: 9
hi guys,

I have improved my patch, hopefully I have covered everything.
Here's the diff file that I promised.
I'll write detail descriptions of what I've done tomorrow, need to go to bed now :-)


Attachments:
callbackpatch.txt [9.63 KiB]
Downloaded 481 times
Top
 Profile  
 
 Post subject:
PostPosted: Sat Jul 21, 2007 11:31 pm 
Offline

Joined: Mon Jul 02, 2007 4:54 am
Posts: 9
Hi guys,

Sorry for the delay. I'll now describe what I've done. Your comments are really appreciated :-)

I think there are 2 main parts to it, the first one is:

Code:
@@ -774,13 +788,91 @@
                  
                  
      // CHECKING THE TIMEOUT               
-      $res_all_calcultimeout = $RateEngine->rate_engine_all_calcultimeout($this, $this->credit);
+      if ($this->mode == 'callback') {
+         // get the ring timeout.
+         $dialparams = $this->agiconfig['dialcommand_param'];
+         $ringtimeout = strtok($dialparams, "|");
+         $this->debug(VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "CALLBACK - RING TIMEOUT = $ringtimeout");
+
+         // determine leg1 answered time up to this point.
+         global $G_startime;
+         global $G_callback_timeout_computed_time;
+         $G_callback_timeout_computed_time = time();
+         $answeredtime_leg1 = $G_callback_timeout_computed_time - $G_startime;
+         $this->debug(VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "CALLBACK - LEG1 ANSWERED TIME SO FAR = $answeredtime_leg1");
+
+         // add the ring timeout.
+         $answeredtime_leg1 = $answeredtime_leg1 + $ringtimeout;
+         $this->debug(VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "CALLBACK - RING TIMEOUT + LEG1 ANSWERED TIME SO FAR = $answeredtime_leg1");
+
+         // compute the cost of leg1 up to this point.
+         // we can ignore freetimetocall stuff.
+         $freetimetocall_used = 0;
+         $RateEngine_leg1->usedratecard = 0;
+         $RateEngine_leg1->rate_engine_calculcost($this, $answeredtime_leg1, 0, $freetimetocall_used);
+         $cost_leg1 = $RateEngine_leg1->lastcost;
+         $credit = $this->credit + $cost_leg1;
+         $this->debug(VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "CALLBACK - ACTUAL CREDIT = $this->credit; LEG1 COST SO FAR = $cost_leg1; EFFECTIVE CREDIT = $credit");
+
+         // RateEngine_leg1 and RateEngine, both belong to the same call plan.
+         // need to halve the free time to call.
+         $RateEngine_leg1->ratecard_obj[0][46] = $RateEngine_leg1->ratecard_obj[0][46] / 2;
+
+         // compute the timeout for leg2.
+         for ($K = 0; $K < count($RateEngine->ratecard_obj); $K++) {
+            $r = 0.5;
+            $r_min = 0.0;
+            $r_max = 1.0;
+            $credit_leg1 = $r * $credit;
+            $credit_leg2 = $credit - $credit_leg1;
+            $timeout_leg1_old = 0;
+            $timeout_leg2_old = 0;
+
+            // RateEngine_leg1 and RateEngine, both belong to the same call plan.
+            // need to halve the free time to call.
+            $RateEngine->ratecard_obj[$K][46] = $RateEngine->ratecard_obj[$K][46] / 2;
+
+            for (;;) {
+               $timeout_leg1 = $RateEngine_leg1->rate_engine_calcultimeout($this, $credit_leg1);
+               $timeout_leg2 = $RateEngine->rate_engine_calcultimeout($this, $credit_leg2, $K);
+               //$this->debug(VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "CALLBACK - CREDIT LEG1 = $credit_leg1; TIMEOUT LEG1 = $timeout_leg1; CREDIT LEG2 = $credit_leg2; TIMEOUT LEG2 = $timeout_leg2");
+
+               if ((substr($timeout_leg1, 0, 5) == 'ERROR') || (substr($timeout_leg2, 0, 5) == 'ERROR')) {
+                  $prompt = "prepaid-no-enough-credit";
+                  $agi->stream_file($prompt, '#');
+                  return -1;
+               }
+
+               if ($timeout_leg1 > $timeout_leg2) {
+                  $r_max = $r;
+                  $r = ($r + $r_min) / 2;
+               }
+               else {
+                  $r_min = $r;
+                  $r = ($r + $r_max) / 2;
+               }
+
+               if (($timeout_leg1 == $timeout_leg1_old) && ($timeout_leg2 == $timeout_leg2_old)) {
+                  $this->debug(VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "CALLBACK - CALCULTIMEOUT: K = $K - TIMEOUT = $timeout_leg2");
+                  break;
+               }
+
+               $credit_leg1 = $r * $credit;
+               $credit_leg2 = $credit - $credit_leg1;
+               $timeout_leg1_old = $timeout_leg1;
+               $timeout_leg2_old = $timeout_leg2;
+            }
+         }
+      }
+      else {
+         $res_all_calcultimeout = $RateEngine->rate_engine_all_calcultimeout($this, $this->credit);
      
-      $this -> debug( VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "RES_ALL_CALCULTIMEOUT ::> $res_all_calcultimeout");
-      if (!$res_all_calcultimeout){                     
-         $prompt="prepaid-no-enough-credit";
-         $agi-> stream_file($prompt, '#');
-         return -1;
+         $this -> debug( VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "RES_ALL_CALCULTIMEOUT ::> $res_all_calcultimeout");
+         if (!$res_all_calcultimeout){                     
+            $prompt="prepaid-no-enough-credit";
+            $agi-> stream_file($prompt, '#');
+            return -1;
+         }
      }


This is where the timeout for the second leg is computed. The basic idea is to divide the available credit into 2 parts, one is used to fund the first leg and the other is used to fund the second leg.

Ideally, we want to divide the credit such that it gives the same amount of timeout for both legs. However, it is not always possible, especially when ur billing block is large. Consider the following example:

available credit = 5 cents.
rate for leg1 = 1 cent/minute.
billing block for leg1 = 60 seconds.
rate for leg2 = 1 cent/minute.
billing block for leg2 = 60 seconds.

Here, we can't divide the credit into 2.5 cents each, because the billing block is 60 seconds. So, we can either divide the credit into 3 cents - 2 cents, or 2 cents - 3 cents.
My patch will divide the credit into 3 cents - 2 cents in this case, 3 cents for first leg and 2 cents for second leg. Effectively, the call will last only for 2 minutes. And after the call is terminated, there should be 1 cent left in the credit.

So, here's what I've done:
    1. Get the maximum "ring" time for the second leg. This is obtained from $this->agiconfig[ 'dialcommand_param' ].
    2. Get leg 1 answered time so far, up to the point where we're about to compute the timeout for the second leg.
    3. Find the sum of times obtain from one and two, and compute the cost for that duration using first leg's rate.
    4. Substract the cost computed in 3 from the available credit. This will be the effective credit that will be used for the timeout computation.
    5. Since both rates belong to the same call plan, we divide the free time to call, if any, by 2, for each ratecard object before computing the timeout.
    6. Then there's the loop that compute the timeout.

The second part is:

Code:
@@ -918,6 +932,15 @@
            
         if ($typecall==1) $timeout = $A2B -> config["callback"]['predictivedialer_maxtime_tocall'];
            
+         // if callback, need to fine tune the timeout.
+         global $G_callback_timeout_computed_time;
+         if ($G_callback_timeout_computed_time > 0) {
+            $elapsedtime = time() - $G_callback_timeout_computed_time;
+            $computedtimeout = $timeout;
+            $timeout = $timeout - $elapsedtime;
+            $A2B->debug(VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "CALLBACK - COMPUTED TIMEOUT = $computedtimeout; ELAPSED TIME SINCE THEN = $elapsedtime; EFFECTIVE TIMEOUT = $timeout");
+         }
+
         $dialparams = str_replace("%timeout%", $timeout *1000, $A2B->agiconfig['dialcommand_param']);
         //$dialparams = "|30|HS($timeout)"; // L(".$timeout*1000.":61000:30000)
            
@@ -1038,6 +1061,15 @@
                  $destination= substr($destination, strlen($removeprefix));
               }
               
+               // if callback, need to fine tune the timeout.
+               global $G_callback_timeout_computed_time;
+               if ($G_callback_timeout_computed_time > 0) {
+                  $elapsedtime = time() - $G_callback_timeout_computed_time;
+                  $computedtimeout = $timeout;
+                  $timeout = $timeout - $elapsedtime;
+                  $A2B->debug(VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "CALLBACK - COMPUTED TIMEOUT = $computedtimeout; ELAPSED TIME SINCE THEN = $elapsedtime; EFFECTIVE TIMEOUT = $timeout");
+               }
+
               $dialparams = str_replace("%timeout%", $timeout *1000, $A2B->agiconfig['dialcommand_param']);
               
               if ($pos_dialingnumber !== false){                  


Here, we compute the elapsed time since the timeout was computed, and substract that from the computed timeout. This is necessary because after the timeout was computed, there's a possibility that 5 to 10 seconds will elapse due to the IVR saying the time to call.

I've also modified the check for minimum credit to call:

Code:
@@ -396,9 +399,20 @@
      $this -> ratecard_obj[$K]['timeout']=0;
      
      // CHECK IF THE USER IS ALLOW TO CALL WITH ITS CREDIT AMOUNT
+      $freetimetocall_used = 0;
+      $this->usedratecard = $K;
+      $this->rate_engine_calculcost($A2B, 1, 0, 0);
+      $mincredit = -($this->lastcost);
+      //global $agi;
+      //$A2B->debug(VERBOSE | WRITELOG, $agi, __FILE__, __LINE__, "MIN CREDIT REQUIRED = $mincredit");
+      if ($credit < $mincredit) {
+         return "ERROR CT1";   // NOT ENOUGH CREDIT.
+      }
+      /*
      if ($credit < $A2B->agiconfig['min_credit_2call']){
         return "ERROR CT1";  //NO ENOUGH CREDIT TO CALL THIS NUMBER
      }
+      */
      
      // if ($rateinitial==0) return "ERROR RATEINITIAL($rateinitial)";
      $TIMEOUT = 0;


Here, we compute the cost of 1 second call and that's the minimum credit required.

Now, if you have:

dialcommand_param = "|60|HL(%timeout%:60000:30000)"

in a2billing.conf, it means that we will reserve 60 seconds worth of conversation. Asiby, u may not like it but I agree with krzykat that there's nothing we can do about it unless there's a way for us to modify the timeout right after the second leg is established. Only then we know the exact ring time.

If u're worried about the clients losing money, please take note that we are not taking anything from the clients. After the call terminated, we will still charge the client accordingly, we don't charge extra due to the 60 seconds reserving thing.

Imagine a client has 5 cents credit in his account. Now, he wants to call a country which has the following rate:

rate = 3 cents/minute.
billing block = 120 seconds.

Of course, the client will not be able to call this particular destination, but he did not lose any money, the money is still there, he can use it to call other destinations.

Same thing here, some of the credit that we previously reserved will still be in the account, which the client can use for normal (non-callback) calls.

Man, please pardon my english.. hope I don't confuse anyone :-)

regards,
-rs-


Top
 Profile  
 
 Post subject:
PostPosted: Sun Jul 22, 2007 2:50 am 
Offline
User avatar

Joined: Mon Apr 30, 2007 6:43 am
Posts: 1060
Location: Canada
Excellent work Meessras. Your English is perfect. I will install you fix on a development server ASAP and test it. The funny thing is that I have a totally different approach that works too :D. I am just finishing some fine tuning and I will release it for testing.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Oct 08, 2007 11:33 pm 
Offline

Joined: Fri Apr 14, 2006 1:36 pm
Posts: 5
Guys,

Here is a n00b question:

How do I do the same thing in V. 1.2.3 ?

We have our V. 1.2.3 pretty heavily customized and redesigned, and this is the only thing that I'm stuck with...

Any help on this will be highly apreciated.

Thanks in advance.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Oct 24, 2008 10:32 pm 
Offline

Joined: Mon Oct 13, 2008 7:15 pm
Posts: 7
check my bug fix / solution for the 1st. leg calltime calculation
http://forum.asterisk2billing.org/viewtopic.php?p=18736 :P


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 41 posts ]  Go to page Previous  1, 2, 3
Hosted Voice Broadcast


All times are UTC


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group