├── README.md ├── cgrates └── cgrates.json ├── cgrates_cdr_processing.png ├── consumer ├── consumer_acc.py └── rated_cdr_consumer.py ├── ib_billing_process.jpg ├── logstash └── 01-opensips_acc.conf └── sample_tariffplans ├── DestinationRate.csv ├── Destinations.csv ├── Rates.csv ├── RatingPlans.csv ├── RatingProfiles.csv └── Timings.csv /README.md: -------------------------------------------------------------------------------- 1 | Billing System for Opensips CDRs using CGRates 2 | = 3 | 4 | ## Introduction 5 | 6 | In this project, you will find all the config files needed to implement a billing system for Opensips CDRs using Cgrates rating engine. 7 | 8 | ## Architecture Topology 9 | 10 | ![topology] 11 | 12 | ## Implementation tutorial 13 | 14 | Please find my tutorials: how to implement a billing system for Opensips using CGRates on my linkedin page. 15 | 16 | * [Part 1 - Billing System for Opensips CDRs][part1] 17 | 18 | * [Part 2 - Billing System for Opensips CDRs][part2] 19 | 20 | * [Part 3 - Billing System for Opensips CDRs][part3] 21 | 22 | 23 | 24 | 25 | [topology]: https://github.com/ksrigo/billing_system/blob/master/ib_billing_process.jpg 26 | [part1]: https://www.linkedin.com/pulse/how-build-billing-system-opensips-cdrs-srigo-kanapathipillai/ 27 | [part2]: https://www.linkedin.com/pulse/how-build-billing-system-opensips-cdrs-part-2-srigo-kanapathipillai-1c/ 28 | [part3]: https://www.linkedin.com/pulse/part-3-billing-system-opensips-cdrs-srigo-kanapathipillai/ 29 | -------------------------------------------------------------------------------- /cgrates/cgrates.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | // Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments 4 | // Copyright (C) ITsysCOM GmbH 5 | 6 | // Added by Srigo on 14/06/18 7 | 8 | "general": { 9 | "default_request_type": "*rated", 10 | "logger":"*syslog", // controls the destination of logs <*syslog|*stdout> 11 | "log_level": 7, 12 | "rounding_decimals": 5, 13 | "poster_attempts": 3, 14 | "default_category": "call", 15 | "default_tenant": "v.ibrowse.com", 16 | "default_timezone": "UTC", 17 | }, 18 | 19 | // Allow us to not run a Stor_db 20 | "stor_db": { // database used to store offline tariff plans and CDRs 21 | "db_type": "*internal", // stor database type to use: 22 | }, 23 | 24 | "data_db": { // database used to store runtime data (eg: accounts, cdr stats) 25 | "db_type": "redis", // data_db type: 26 | "db_host": "https://www.linkedin.com/redir/invalid-link-page?url=127%2e0%2e0%2e1", // data_db host address 27 | "db_port": 6379, // data_db port to reach the database 28 | "db_name": "10", // data_db database name to connect to 29 | "db_user": "cgrates", // username to use when connecting to data_db 30 | "db_password": "", // password to use when connecting to data_db 31 | "load_history_size": 10, // Number of records in the load history 32 | }, 33 | 34 | "listen": { 35 | "http": "https://www.linkedin.com/redir/invalid-link-page?url=0%2e0%2e0%2e0%3A2080", // HTTP listening address 36 | }, 37 | 38 | "http": { 39 | "json_rpc_url": "/jsonrpc" 40 | }, 41 | 42 | "rals": { 43 | "enabled": true, 44 | }, 45 | 46 | // Enable CDR Server 47 | 48 | "cdrs": { 49 | "enabled": true, // start the CDR Server service: 50 | "store_cdrs": false, 51 | "pubsubs_conns": [], // address where to reach the pubusb service, empty to disable pubsub functionality: <""|*internal|x.y.z.y:1234> 52 | "attributes_conns": [], // address where to reach the attribute service, empty to disable attributes functionality: <""|*internal|x.y.z.y:1234> 53 | "users_conns": [], // address where to reach the user service, empty to disable user profile functionality: <""|*internal|x.y.z.y:1234> 54 | "aliases_conns": [], // address where to reach the aliases service, empty to disable aliases functionality: <""|*internal|x.y.z.y:1234> 55 | "cdrstats_conns": [], // address where to reach the cdrstats service, empty to disable cdrstats functionality: <""|*internal|x.y.z.y:1234> 56 | "thresholds_conns": [], // address where to reach the thresholds service, empty to disable thresholds functionality: <""|*internal|x.y.z.y:1234> 57 | "stats_conns": [], // address where to reach the stat service, empty to disable stats functionality: <""|*internal|x.y.z.y:1234> 58 | "online_cdr_exports":["amqp_cdr"] // EXPORT RATED CDR to Rabbitmq 59 | }, 60 | 61 | 62 | "cdrstats": { 63 | "enabled": false, // starts the cdrstats service: 64 | }, 65 | //CDR Exporting service 66 | "cdre": { 67 | // Rated CDR through Rabbitmq 68 | "amqp_cdr": { 69 | "export_format": "*amqp_json_map", 70 | "export_path": "amqp://XXXXX:XXXXXX@XXXXX.ibrowse.com:5672/system?queue_id=rated_cdr&heartbeat=30", 71 | "attempts": 3, 72 | "content_fields": [ // template of the exported content fields 73 | {"tag": "CGRID", "type": "*composed", "value": "CGRID", "field_id": "CGRID"}, 74 | {"tag":"RunID", "type": "*composed", "value": "RunID", "field_id": "RunID"}, 75 | {"tag":"TOR", "type": "*composed", "value": "ToR", "field_id": "ToR"}, 76 | {"tag":"OriginID", "type": "*composed", "value": "OriginID", "field_id": "OriginID"}, 77 | {"tag":"OriginHost", "type": "*composed", "value": "OriginHost", "field_id": "OriginHost"}, 78 | {"tag":"RequestType", "type": "*composed", "value": "RequestType", "field_id": "RequestType"}, 79 | {"tag":"Direction", "type": "*composed", "value": "Direction", "field_id": "Direction"}, 80 | {"tag":"Tenant", "type": "*composed", "value": "Tenant", "field_id": "Tenant"}, 81 | {"tag":"Category", "type": "*composed", "value": "Category", "field_id": "Category"}, 82 | {"tag":"Account", "type": "*composed", "value": "Account", "field_id": "Account"}, 83 | {"tag":"Subject", "type": "*composed", "value": "Subject", "field_id": "Subject"}, 84 | {"tag":"Destination", "type": "*composed", "value": "Destination", "field_id": "Destination"}, 85 | {"tag":"SetupTime", "type": "*composed", "value": "SetupTime", "layout": "2006-01-02T15:04:05Z07:00", "field_id": "SetupTime"}, 86 | {"tag":"AnswerTime", "type": "*composed", "value": "AnswerTime", "layout": "2006-01-02T15:04:05Z07:00", "field_id": "AnswerTime"}, 87 | {"tag":"Usage", "type": "*composed", "value": "Usage{*duration_seconds}", "field_id": "Usage"}, 88 | {"tag":"Cost", "type": "*composed", "value": "Cost", "field_id": "Cost"}, 89 | {"tag":"Room", "type": "*composed", "value": "room", "field_id": "Room"}, 90 | {"tag":"Bill_to", "type": "*composed", "value": "bill_to", "field_id": "Bill_to"}, 91 | {"tag":"Tech_realm", "type": "*composed", "value": "tech_realm", "field_id": "Tech_realm"}, 92 | {"tag":"ConnectFee", "type": "*composed", "field_id": "ConnectFee", "value": "~CostDetails:s/\"ConnectFee\":(.*),\"RoundingMethod/${1}/"}, 93 | {"tag":"MatchedDestinationID", "type": "*composed", "field_id": "DestName", "value": "~CostDetails:s/\"DestinationID\":\"(.*)\",\"DestinationPrefix/${1}/"}, 94 | {"tag":"MatchedPrefix", "type": "*composed", "field_id": "MatchedPrefix", "value": "~CostDetails:s/\"DestinationPrefix\":\"(.*)\",\"RatingPlanID/${1}/"} 95 | ] 96 | } 97 | }, 98 | 99 | 100 | "pubsubs": { 101 | "enabled": false, // starts PubSub service: . 102 | }, 103 | 104 | 105 | "aliases": { 106 | "enabled": false, // starts PubSub service: . 107 | }, 108 | 109 | 110 | "users": { 111 | "enabled": false, // starts User service: . 112 | "indexes": ["Uuid"], // user profile field indexes 113 | }, 114 | 115 | 116 | "sessions": { 117 | "enabled": true, 118 | }, 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /cgrates_cdr_processing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksrigo/billing_system/d56e96a5835657decba8a2bb6620e810dbbe5876/cgrates_cdr_processing.png -------------------------------------------------------------------------------- /consumer/consumer_acc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | #Created on 22/06/2018 4 | #Author: Srigo Kana 5 | 6 | import pika 7 | import logging 8 | import json 9 | 10 | SERVICE_NAME = 'cgrates' 11 | SERVICE_VERSION = '1' 12 | SERVICE_DESCRIPTION = 'cgrates billing system' 13 | RABBIT_PASSWORD = "********" 14 | RABBIT_ADDRESS = "XXXXX.ibrowse.com" 15 | 16 | #CGRATES API: 17 | URL = "http://cgrates.ibrowse.com:2080/jsonrpc" 18 | HEADERS = {'content-type': 'application/json'} 19 | 20 | 21 | class CgratesService(): 22 | def __init__(self, logger): 23 | self.logger = logger 24 | self.connection_parameters = pika.ConnectionParameters(host=RABBIT_ADDRESS, virtual_host='system', credentials=pika.PlainCredentials('service', RABBIT_PASSWORD), heartbeat_interval=300) 25 | self.version = SERVICE_VERSION 26 | self.service = SERVICE_NAME 27 | self.description = SERVICE_DESCRIPTION 28 | self.exchange= 'service_' + SERVICE_NAME 29 | self.exchange_type = 'topic' 30 | self.queue = '{service}.{version}'.format(service=SERVICE_NAME, version=SERVICE_VERSION) 31 | self.queue_arguments = {'x-expires':60000} 32 | self.routing_key= '{service}.{version}.*'.format(service=SERVICE_NAME, version=SERVICE_VERSION) 33 | self.exchange_routing_key = self.service + '.*.*' 34 | 35 | # Step #1: Connect to RabbitMQ 36 | def start(self): 37 | #self.load() 38 | parameters = self.connection_parameters 39 | self._connection = pika.SelectConnection(parameters, self._on_connected) 40 | 41 | try: 42 | # Loop so we can communicate with RabbitMQ 43 | self._connection.ioloop.start() 44 | except KeyboardInterrupt: 45 | # Gracefully close the connection 46 | self._connection.close() 47 | # Loop until we're fully closed, will stop on its own 48 | self._connection.ioloop.start() 49 | 50 | # Step #2 Create a new channel 51 | def _on_connected(self, connection): 52 | """Called when we are fully connected to RabbitMQ""" 53 | #Add a callback notification when the connection has closed. 54 | self._connection.add_on_close_callback(self._on_connection_closed) 55 | # Creating a new channel 56 | self._connection.channel(self._on_channel_open) 57 | 58 | # Step #3 callback for connection close 59 | def _on_connection_closed(self, connection, reply_code=None, reply_text="Unspecified"): 60 | #Triggered on connection close 61 | self._connection.ioloop.stop() 62 | 63 | # Step #4 callback channel open 64 | def _on_channel_open(self, channel): 65 | #Triigered on Channel connected 66 | self._channel = channel 67 | #Pass a callback function that will be called when the channel is closed. 68 | self._channel.add_on_close_callback(self._on_channel_closed) 69 | #Create the exchange 70 | self.logger.info('Declaring exchange {ex_n}'.format(ex_n=self.exchange)) 71 | self._channel.exchange_declare(self._on_exchange_declareok, self.exchange, self.exchange_type, durable=True, internal = True) 72 | 73 | # Step #5 callback for channel close 74 | def _on_channel_closed(self, channel, reply_code, reply_text): 75 | #triggered on channel close 76 | self.logger.warning('Channel {ch_n} was closed: ({code}) {txt}'.format( 77 | ch_n=channel, code=reply_code, txt=reply_text)) 78 | self._connection.close() 79 | 80 | # Step #6 callback exchange declare 81 | def _on_exchange_declareok(self, unused_frame): 82 | #once exchange is created, create the queue 83 | self.logger.info('Exchange declared') 84 | self.logger.info('Declaring queue {q_n}'.format(q_n=self.queue)) 85 | self._channel.queue_declare(self._on_queue_declareok, queue=self.queue, durable = True, auto_delete = False, arguments = self.queue_arguments) 86 | 87 | # Step #7 callback queue declare 88 | def _on_queue_declareok(self, method_frame): 89 | self.logger.info('Binding {ex} to {q} with {rk}'.format( 90 | ex=self.exchange, q=self.queue, rk=self.routing_key)) 91 | self._channel.queue_bind(self._on_bindok_queue, queue=self.queue, 92 | exchange=self.exchange, routing_key=self.routing_key) 93 | 94 | # Step #8 bind exchnage to the queue 95 | def _on_bindok_queue(self, unused_frame): 96 | self.logger.info('Queue bound') 97 | self._consumer_tag = self._channel.basic_consume(self.on_message, self.queue) 98 | 99 | # Step #9 Call Cgrates API and consume message 100 | def on_message(self, unused_channel, basic_deliver, properties, body): 101 | self.logger.debug('Received message #{msg} from {rpt}: {body}'.format(msg=basic_deliver.delivery_tag, rpt=properties.reply_to, body=body)) 102 | data = json.loads(body) 103 | req_host = data['acc_details']['req_uri'].split("@")[1] 104 | if req_host == 'external.call': 105 | account = data['acc_details']['from_uri'].split("@")[0] 106 | destination = data['acc_details']['req_uri'].split("@")[0] 107 | answer_time = data['acc_details']['call_start_time'] 108 | setup_time = data['acc_details']['created'] 109 | duration = data['acc_details']['duration'] 110 | ms_duration = data['acc_details']['ms_duration'] 111 | call_id = data['acc_details']['call_id'] 112 | bill = data['acc_details']['bill'].split("@")[0] 113 | tech_realm = data['acc_details']['tech_realm'] 114 | required_perm = data['acc_details']['required_perms'] 115 | SUBJECT = "DEFAULT_RATING" 116 | params = { 117 | "Direction":"*out", 118 | "Category": "call", 119 | "RequestType": "*rated", 120 | "ToR": "*voice", 121 | "Tenant": "ibrowse.com", 122 | "Account": account, 123 | "Destination": destination, 124 | "AnswerTime": answer_time, 125 | "SetupTime": setup_time, 126 | "Usage": duration, 127 | "OriginID": call_id, 128 | "Subject": SUBJECT, 129 | "ExtraFields":{ 130 | "tech_realm": tech_realm, 131 | "bill_to": bill, 132 | "perm": required_perm, 133 | } 134 | } 135 | payload = { 136 | "method": "CdrsV1.ProcessExternalCDR", 137 | "jsonrpc": "2.0", 138 | "params": [params] 139 | } 140 | 141 | #Call CGRATES API 142 | try: 143 | response = requests.post(URL, data=json.dumps(payload), headers=HEADERS).json() 144 | result = response['result'] 145 | if result == 'OK': 146 | self.logger.debug('Acknowledging message {tag}'.format(tag=basic_deliver.delivery_tag)) 147 | self._channel.basic_ack(basic_deliver.delivery_tag) 148 | else: 149 | self.logger.debug('Error occured, not acknowledging message {tag}'.format(tag=basic_deliver.delivery_tag)) 150 | self._channel.basic_nack(basic_deliver.delivery_tag, requeue=True) 151 | except Exception, e: 152 | self.logger.debug('Error occured, not acknowledging message {tag}'.format(tag=basic_deliver.delivery_tag)) 153 | self._channel.basic_nack(basic_deliver.delivery_tag, requeue=True) 154 | else: 155 | self.logger.info("Not a PSTN call [{call_id}] no need to rate".format(call_id = data['acc_details']['call_id'])) 156 | self._channel.basic_ack(basic_deliver.delivery_tag) 157 | 158 | 159 | if __name__ == '__main__': 160 | LOG_LEVEL = 'INFO' 161 | LOG_FORMAT = ("%(levelname) -10s" 162 | "%(asctime)s %(name)" 163 | " -30s %(funcName)" 164 | " -35s %(lineno)" 165 | " -5d: %(message)s") 166 | logging.basicConfig(format=LOG_FORMAT, level=LOG_LEVEL) 167 | logger = logging.getLogger(__name__) 168 | basic_service = CgratesService(logger) 169 | basic_service.start() -------------------------------------------------------------------------------- /consumer/rated_cdr_consumer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | #Created on 07/07/2018 4 | #Author: Srigo Kana 5 | 6 | import pika 7 | import json 8 | from ibtools.database import postgre 9 | 10 | RABBIT_PASSWORD = "XXXXX" 11 | RABBIT_ADDRESS = "XXXX.ibrowse.com" 12 | QUEUE = 'rated_cdr' 13 | 14 | HOST = 'XXXXX.ibrowse.com' 15 | PORT = 5432 16 | USER = 'XXXX' 17 | PASSWORD = 'XXXXX' 18 | RDBM = 'XXXX' 19 | 20 | ADD_DETAILED_CDR = """ 21 | INSERT INTO voice_billing.rated_cdr 22 | ({fields}) 23 | VALUES 24 | ({vals}) 25 | RETURNING id 26 | """ 27 | 28 | class CgratesService(): 29 | def __init__(self, logger): 30 | self.logger = logger 31 | self.connection_parameters = pika.ConnectionParameters(host=RABBIT_ADDRESS, virtual_host='system', credentials=pika.PlainCredentials('service', RABBIT_PASSWORD), heartbeat_interval=300) 32 | self.queue = QUEUE 33 | 34 | def _load(self): 35 | self.db = postgre.Postgre(self.logger, host=HOST, port=PORT, user=USER, password=PASSWORD, rdbm=RDBM) 36 | self.db.connect() 37 | 38 | # Step #1: Connect to RabbitMQ 39 | def start(self): 40 | self._load() 41 | parameters = self.connection_parameters 42 | self._connection = pika.SelectConnection(parameters, self._on_connected) 43 | self.logger.info('Connecting to rabbitmq: ' + RABBIT_ADDRESS) 44 | try: 45 | # Loop so we can communicate with RabbitMQ 46 | self._connection.ioloop.start() 47 | except KeyboardInterrupt: 48 | # Gracefully close the connection 49 | self._connection.close() 50 | # Loop until we're fully closed, will stop on its own 51 | self._connection.ioloop.start() 52 | 53 | # Step #2 Create a new channel 54 | def _on_connected(self, connection): 55 | """Called when we are fully connected to RabbitMQ""" 56 | #Add a callback notification when the connection has closed. 57 | self._connection.add_on_close_callback(self._on_connection_closed) 58 | # Creating a new channel 59 | self._connection.channel(self._on_channel_open) 60 | 61 | # Step #3 callback for connection close 62 | def _on_connection_closed(self, connection, reply_code=None, reply_text="Unspecified"): 63 | #Triggered on connection close 64 | self._connection.ioloop.stop() 65 | 66 | # Step #4 callback channel open 67 | def _on_channel_open(self, channel): 68 | self.logger.info("Adding channel open callback") 69 | #Triigered on Channel connected 70 | self._channel = channel 71 | #Pass a callback function that will be called when the channel is closed. 72 | self._channel.add_on_close_callback(self._on_channel_closed) 73 | self._consumer_tag = self._channel.basic_consume(self._on_message, self.queue) 74 | 75 | # Step #5 callback for channel close 76 | def _on_channel_closed(self, channel, reply_code, reply_text): 77 | #triggered on channel close 78 | self.logger.warning('Channel {ch_n} was closed: ({code}) {txt}'.format( 79 | ch_n=channel, code=reply_code, txt=reply_text)) 80 | self._connection.close() 81 | 82 | # Step #6 Consuming messages 83 | def _on_message(self, unused_channel, basic_deliver, properties, body): 84 | self.logger.debug('Received message #{msg} from {rpt}: {body}'.format(msg=basic_deliver.delivery_tag, rpt=properties.reply_to, body=body)) 85 | data = json.loads(body) 86 | #insert to Rated_cdr table 87 | if not self.insert_rated_cdr(data) and data['RunID'] != "*raw": 88 | self.logger.info('Need to requeue or put in another queue. for nw jst ack') 89 | self._channel.basic_ack(basic_deliver.delivery_tag) 90 | #self._channel.basic_nack(basic_deliver.delivery_tag,requeue=True) 91 | else: 92 | self._channel.basic_ack(basic_deliver.delivery_tag) 93 | 94 | # Step #7 Insert rated CDR to the DB 95 | def insert_rated_cdr(self, data): 96 | try: 97 | if data['RunID'] != "*raw": 98 | data_insert = dict() 99 | data_insert['call_id'] = data['OriginID'] 100 | data_insert['bill_to'] = data['Bill_to'] 101 | data_insert['caller'] = data['Account'] 102 | data_insert['callee'] = data['Destination'] 103 | data_insert['tech_realm'] = data['Tech_realm'] 104 | data_insert['room'] = data['Room'] 105 | data_insert['rating_profile'] = data['Subject'] 106 | data_insert['call_setup_time'] = data['SetupTime'] 107 | data_insert['call_answered_time'] = data['AnswerTime'] 108 | data_insert['call_duration'] = data['Usage'] 109 | data_insert['destination_name'] = data['DestName'] 110 | data_insert['service_bill'] = 0 111 | data_insert['prefix_match'] = data['MatchedPrefix'] 112 | data_insert['bill'] = data['Cost'] 113 | data_insert['connect_fee'] = data['ConnectFee'] 114 | 115 | default_req = ADD_DETAILED_CDR 116 | keys = list(data_insert.keys()) 117 | vals = ['%(' + val + ')s' for val in keys if data_insert[val]] 118 | keys = [key for key in keys if data_insert[key]] 119 | req = default_req.format( 120 | fields=', '.join(keys), vals=', '.join(vals)) 121 | res, rows = self.db.execute_safe_query(req, data_insert, select=True) 122 | if res and 'id' in rows[0]: 123 | self.logger.info(rows) 124 | return True 125 | except Exception, e: 126 | self.logger.info(e) 127 | return False 128 | -------------------------------------------------------------------------------- /ib_billing_process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksrigo/billing_system/d56e96a5835657decba8a2bb6620e810dbbe5876/ib_billing_process.jpg -------------------------------------------------------------------------------- /logstash/01-opensips_acc.conf: -------------------------------------------------------------------------------- 1 | input { 2 | beats { 3 | port => 5044 4 | client_inactivity_timeout => 300 5 | } 6 | } 7 | filter { 8 | # This filter is only used for parsing accouting type logs 9 | if [log_type] == "accounting"{ 10 | grok{ 11 | # parse our current syslog format: acc_data will contain call accounting 12 | break_on_match => false 13 | match => ['message',"%{SYSLOGBASE} ACC: %{GREEDYDATA:acc_type}: %{GREEDYDATA:acc_data}"] 14 | } 15 | # Transform csv format logs into Key Value format 16 | kv { 17 | allow_duplicate_values => false 18 | field_split => ";" 19 | source => "acc_data" 20 | target => "acc_details" 21 | remove_field => ["message", "acc_data"] 22 | } 23 | # Find out accouting type (cdr, missed, misc) 24 | if [acc_type] == "request accounted" or [acc_type] == "transaction answered"{ 25 | mutate { 26 | add_field => { "[acc_details][created]" => "%{acc_details[timestamp]}" } 27 | update => { "acc_type" => "misc" } 28 | remove_field => [ "[acc_details][bill]" ] 29 | } 30 | }else if [acc_type] == "call ended"{ 31 | mutate { 32 | update => { "acc_type" => "cdr" } 33 | } 34 | }else if [acc_type] == "call missed"{ 35 | mutate { 36 | update => { "acc_type" => "missed" } 37 | remove_field => [ "[acc_details][bill]","[acc_details][required_perms]" ] 38 | } 39 | } 40 | } 41 | } 42 | output { 43 | # only accounting logs need to use this output 44 | if [log_type] == "accounting"{ 45 | #Send all accouting to this exchange (for DB insert) 46 | rabbitmq { 47 | id => "voice_acc" 48 | exchange => "service_acc" 49 | exchange_type => "topic" 50 | heartbeat => 30 51 | host => "xxxxxxx.ibrowse.com" 52 | password => "****" 53 | user => "user" 54 | vhost => "system"# This is our routing_key, you can adjust it as you wish. 1 is version 1. 55 | key => "voice_acc.1.to_database" 56 | } 57 | # Send only CDR to this exchange (to be rated by CGRates) 58 | if [acc_type] == "cdr"{ 59 | rabbitmq { 60 | id => "cgrates" 61 | exchange => "service_cgrates" 62 | exchange_type => "topic" 63 | heartbeat => 30 64 | host => "xxxxxxx.ibrowse.com" 65 | password => "***" 66 | user => "user" 67 | vhost => "system" 68 | key => "cgrates.1.to_cgrates" 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /sample_tariffplans/DestinationRate.csv: -------------------------------------------------------------------------------- 1 | #id, destinationId, ratesTag, roundingMethod, roundingDecimals, maxCost, maxCostStrategy 2 | DR_FRA_40CNT_0STP,FRA,RT_40CNT_0STP,*up,4,0, 3 | DR_FRA_MOB_50CNT_0STP,FRA_MOB,RT_50CNT_0STP,*up,4,0, -------------------------------------------------------------------------------- /sample_tariffplans/Destinations.csv: -------------------------------------------------------------------------------- 1 | #id, prefix 2 | FRA,33 3 | FRA_MOB,336 4 | FRA_MOB,337 -------------------------------------------------------------------------------- /sample_tariffplans/Rates.csv: -------------------------------------------------------------------------------- 1 | #id,connectfee,rate,RateUnit,RateIncrement,GroupIntervalStart 2 | RT_40CNT_0STP,0,0.04,60s,60s,0s 3 | RT_50CNT_0STP,0,0.05,60s,60s,0s -------------------------------------------------------------------------------- /sample_tariffplans/RatingPlans.csv: -------------------------------------------------------------------------------- 1 | #id,destinationratesid,timingtag,weight 2 | RP_HOTEL,DR_FRA_40CNT_0STP,*any,10 3 | RP_HOTEL,DR_FRA_MOB_50CNT_0STP,*any,10 4 | RP_GUEST,DR_FRA_40CNT_0STP,*any,10 5 | RP_GUEST,DR_FRA_MOB_50CNT_0STP,*any,10 -------------------------------------------------------------------------------- /sample_tariffplans/RatingProfiles.csv: -------------------------------------------------------------------------------- 1 | #Direction,Tenant,Category,Subject,ActivationTime,RatingPlanId,RatesFallbackSubject,CdrStatQueueIds 2 | *out,v.ibrowse.com,call,GUEST_RATING,2012-01-01T00:00:00Z,RP_GUEST,, 3 | *out,v.ibrowse.com,call,HOTEL_RATING,2012-01-01T00:00:00Z,RP_HOTEL,, -------------------------------------------------------------------------------- /sample_tariffplans/Timings.csv: -------------------------------------------------------------------------------- 1 | #Tag,Years,Months,MonthDays,WeekDays,Time 2 | ALWAYS,*any,*any,*any,*any,00:00:00 3 | ASAP,*any,*any,*any,*any,*asap --------------------------------------------------------------------------------