CDRTool prepaid functionality ----------------------------- The prepaid functionality is achieved by the combination of: 1. OpenSIPS call_control module, available from version 1.5 call_control module provides the interface between OpenSIPS dialog module to the external Call Control application. 2. Call Control application available at http://callcontrol.ag-projects.com Call Control maintains the session status and terminates the sessions by sending BYE messages to both SIP end-points. The session control module communicates with both the SIP Proxy and CDRTool using the API described below. 3. CDRTool rating engine Rating engine calculates the maximum session time based on subscriber balance and destination using the same algorithm as the postpaid engine Show Price function and debits the subscriber balance. Prepaid accounts are managed in the Prepaid section of Rating web page. Prepaid API ----------- CDRTool prepaid engine is used to compute the maximum session time based on a subscriber balance and debits the balance when the session has ended. The prepaid engine is accessible over the TCP socket where the rating engine listens. The connection can be tested manually using the telnet command and by typing help at the prompt: tstuser@host:~$ telnet 10.0.0.0.1 9024 Trying 10.0.0.1... Connected to 10.0.0.1. Escape character is '^]'. Help MaxSessionTime CallId=6432622xvv@1 From=sip:123@example.com To=sip:0031650222333@example.com Duration=7200 Gateway=10.0.0.1 EnumTLD=e164.arpa ShowPrice From=sip:123@example.com To=sip:0031650222333@example.com Gateway=10.0.0.1 Duration=59 EnumTLD=e164.arpa DebitBalance CallId=6432622xvv@1 From=sip:123@example.com To=sip:0031650222333@example.com Gateway=10.0.0.1 Duration=59 EnumTLD=e164.arpa AddBalance From=123@example.com Value=10.00 GetBalance From=123@example.com GetBalanceHistory From=123@example.com DeleteBalance From=123@example.com DeleteBalanceHistory From=123@example.com In the example above only the commands related to the prepaid functionality are highlighted. The session control module must send commands based on the syntax described below, to the CDRTool prepaid engine. A command is a line of text containing a keyword, which is the command itself followed by a number of parameters separated by space. The command ends with an enter in unix style: '\n' For each such request there will be a reply on one or more lines, containing information that depends on the command that was sent. A reply ends when a double end of line (\n) is received: \n\n (this is needed because there are commands that require multiline responses). How the information contained in a reply is to be interpreted depends on the command itself and is described below. 1. Asking for the session time limit ------------------------------ To request the maximum time for a new session, the following request must be sent to CDRTool rating engine (on one single line): MaxSessionTime CallId=6432622xvv@1 From=sip:123@example.com To=sip:0031650222@example.com Duration=7200 Gateway=81.20.68.10 ENUMtl=tld.com\n MaxSessionTime request is created when a new INVITE arrives at the SIP Proxy/session control application. Depending on the session setup time, the maximum session time might not be accurate because the session setup time is unknown at the INVITE stage. You can call MaxSessionTime again when the session has been setup (the OK has been received), in which case the rating engine will provide the updated maximum session time for the session that was in progress. Duration is a limit imposed to the time allowed, in case you want to limit the session time to a maximum duration. This will be a top limit for the session's time, even if the user could talk more than that based on his current balance. The session control module may limit all sessions to 7200 seconds for example. To may actually be sip:number@domain;param=value so you must extract the username part which is the number From is a sip URI which can be "full name". ENUMtld parameter is optional but if present discounts based on ENUM can be applied (see RATING.txt for more information on ENUM discounts). Call control expects a reply like below: value\n\n value is the numeric time in seconds, or None, None meaning it's not a prepaid account and it doesn't have any limitation, or it's a free destination, like a 0800 number. To field must be the canonical destination SIP URI after all possible lookups in the SIP Proxy. 2. Debiting the balance when the conversation ends ----------------------------------------------- To debit money from the user balance when the conversation ends, the session control module must send the following command on one line: DebitBalance CallId=6432622xvv@1 From=sip:123@example.com To=sip:0031650222@example.com Gateway=81.20.68.10 Duration=59\n Same notes about the From and To fields as above apply. The call id must match the call id provided to the correspondent MaxSessionTime command. and expects in return: value maxsessiontime\n\n where value is one of: OK (success), Failed (failure), Not Prepaid (account is not prepaid - this case is not considered a failure). The second line contains the new maximum session time that is valid after debiting the balance for this session. It can be used to update the maximum session time of other sessions that are already in progress for the same user. Note that once your have called once MaxSessiontime, you must also call the correspondent DebitBalance even if the session has zero seconds duration. If you do not do this, the session will stay in progress for the maximum session time and any subsequent session setup will have its maximum duration altered by this existing session in progress. Sessions in progress will be automatically purged after they expire when a new MaxSessionTime request for the same user is received. The debited value and the new balance is recorded in the prepaid_history table. If the session does not exist, no balance will be debited. To force the DebitBalance to debit the balance even if no session in progress exists, you can supply Force=1 parameter. This can be useful if for some reason the requests have been lost due to network problems. In such case the requests can be applied at a later time. Prepaid ongoing sessions ------------------------ To see the prepaid sessions in progress go to Rating -> Prepaid. Fill in the Sesssions field: >0 and click the Search button. Notes ----- The balance is debited correctly regardless of the moment of CDR normalization. CDR re-normalization of sessions does not recalculate balance. Simultaneous prepaid sessions ----------------------------- It is possible to have multiple sessions using the same prepaid account without the risk of reaching a negative balance. The modus operandi is based on a simple model: all prepaid sessions belonging to the same account must end at the same moment regadless of the called destinations and their different prices. The rating engine simply calculates that moment based on the data available from ongoing sessions, available balance and the new session setup request. This scheme provides a fair balancing policy with small performance penalty on the servers. By using this simple model there is no need to poll any database during ongoing sessions, which could become major scalability problem. All the work is done only once at session setup time. This will increase the load on the rating engine only during session setup and only for the users that have parallel sessions, the rating engine will perform internally during each session setup 2 x the number of sessions sessions to ShowPrice and MaxSessionTime functions. To be entirely accurate the session control must also connect back to the rating engine when the session has been setup so that the session expiration can be updated. You must set $RatingEngine['prepaid_lock'] to 0 in etc/cdrtool/global.inc and update the session control software module that keeps track of the maximum session time to enforce session ending for all sessions belonging to the same sip account at the same time based on the last value of MaxSessionTime received from the rating engine. The previous version required the session control module to keep the time counter per sip session, now it needs to sync the counter for all sessions belonging to the same sip account. See below the pseudo code for the session control engine. The list of ongoing prepaid sessions can be monitored in the Prepaid accounts section of the Rating pages. For each sip account that has ongoing sessions a link marked Sessions apears in the left side of the prepaid record. Click on it to display the ongoing prepaid sessions. The 'Delete session' button allows the removal of the session form the rating engine. The expired sessions that have not been closed properly by the session control module are automatically purged 120 seconds after expiration, which has been chosen to cover a reasonable session setup time. The time and balance distribution between parallel sessions is logged detailed in the syslog: cdrtool[13292]: MaxSessionTime From=sip:adi@umts.ro Lock=1 To=sip:00318008185@umts.ro CallId=waaivgq89aTxmPIG6JIsPhODUe0tRVf- Duration=36000 Gateway=80.101.96.20 cdrtool[13292]: ConnectFee=0.0450 Span=1 Duration=16 DestId=31646 default Profile=442 Period=weekend Rate=442 Interval=0-24 Cost=0.1600/60 Price=0.0427 cdrtool[13292]: Ongoing prepaid session uw1znivnqoeofyezzuiiisudukw64cm- for adi@umts.ro to sip:0031646999425@umts.ro: duration=16, price=0.0877 cdrtool[13292]: Balance for adi@umts.ro having 1 ongoing sessions: database=9.9534, due=0.0877, real=9.8657 cdrtool[13292]: Maximum duration for adi@umts.ro to destination 31646 having balance=9.8657 is 3700 cdrtool[13292]: Maximum duration for adi@umts.ro to destination 31800 having balance=9.8657 is 29597 cdrtool[13292]: Maximum duration agregated for adi@umts.ro is (Balance=9.8657)/(Sum of price per second for each destination=0.0030)=3288 s cdrtool[13292]: CallId=waaivgq89atxmpig6jisphodue0trvf- BillingParty=adi@umts.ro DestId=31800 Balance=9.8657 MaxSessionTime=3288 Spans=2 Pseudo-code for rating engine ----------------------------- This code is actually implemented in cdrtool. max_session_time (session, from, to) { list(balance,active_sessions)=get_prepaid_account_from_database(); if (count(active_sessions[from]) > 0) { if (purge_expired_sessions()) { // reload prepaid data list(balance,active_sessions)=get_prepaid_account_from_database(); } max=calculate_aggregated_maxsession_time(active_sessions[from],balance) return max } else { // is the first session rate = new Rate(session) active_sessions[from][]=session return rate->max_session_time() } } calculate_aggregated_maxsession_time(active_sessions,balance) { // simulate session termination for all ongoing sessions foreach (session in active_sessions[from]) { due_balance=used_balance+show_price(session) } // calculate new virtual balance balance_new=balance-due_balance // simulate we start parallel sessions from scratch at this moment // including the new session setup request // we skip any connect cost for session already in progress active_sessions_new[from]=active_sessions[from] active_sessions_new[from][]=session foreach (session in active_sessions_new[from]) { rate = new Rate(session) maxsessiontime=rate->max_session_time() // calculate price per second for each session price_per_second=price_per_second+balance_new/maxsessiontime } return balance/price_per_second } debit_balance(session, from, to, duration) { list(balance,active_sessions)=get_prepaid_account_from_database(); foreach (session in active_sessions[from]) { if (session == session) continue new_active_sessions[] = session } // update list of active sessions active_sessions[from]=new_active_sessions price=showprice(session,from,to,duration) balance=balance-price update_prepaid_account_to_database(balance,active_sessions); if (count(active_sessions[from]) > 0) { max=calculate_aggregated_maxsession_time() } else { max=0 } return max } Pseudo-code for session control engine -------------------------------------- on_session_setup () { max=cdrtool_max_session_time(session, from, to) if (sip_connect_session() == true) { // add session to the list of active sessions // update tiomer again becuase we had a variable setup time max=cdrtool_max_session_time(session, from, to) active_sessions[from][]=session // set maxsession time for all remaining sessions of the same user foreach (session in active_sessions[from]) { session.maxsessiontime = max } } } on_session_disconnect () { sip_disconnect_session(); max=cdrtool_debit_balance(session, from, to, duration) // remove session from the list of active sessions foreach (session in active_sessions[from]) { if (session == session) continue new_active_sessions[] = session } // update list of active sessions active_sessions[from]=new_active_sessions foreach (session in active_sessions[from]) { session.maxsessiontime = max } }