Improved SureTax configuration with defaults, modified NewSureTaxRequest method

This commit is contained in:
DanB
2015-10-18 14:24:15 +02:00
parent 831e633281
commit 5d56411b5b
9 changed files with 430 additions and 141 deletions

View File

@@ -31,15 +31,14 @@ import (
)
const (
DISABLED = "disabled"
JSON = "json"
GOB = "gob"
POSTGRES = "postgres"
MONGO = "mongo"
REDIS = "redis"
SAME = "same"
FS = "freeswitch"
CFG_RELOADS = "cfg_reloads_"
DISABLED = "disabled"
JSON = "json"
GOB = "gob"
POSTGRES = "postgres"
MONGO = "mongo"
REDIS = "redis"
SAME = "same"
FS = "freeswitch"
)
var (
@@ -70,6 +69,8 @@ func NewDefaultCGRConfig() (*CGRConfig, error) {
cfg.ConfigReloads[utils.CDRC] <- struct{}{} // Unlock the channel
cfg.ConfigReloads[utils.CDRE] = make(chan struct{}, 1)
cfg.ConfigReloads[utils.CDRE] <- struct{}{} // Unlock the channel
cfg.ConfigReloads[utils.SURETAX] = make(chan struct{}, 1)
cfg.ConfigReloads[utils.SURETAX] <- struct{}{} // Unlock the channel
cgrJsonCfg, err := NewCgrJsonCfgFromReader(strings.NewReader(CGRATES_CFG_JSON))
if err != nil {
return nil, err
@@ -238,8 +239,8 @@ type CGRConfig struct {
MailerAuthUser string // Authenticate to email server using this user
MailerAuthPass string // Authenticate to email server with this password
MailerFromAddr string // From address used when sending emails out
SureTaxCfg *SureTaxCfg // Load here SureTax configuration, as pointer so we can have runtime reloads in the future
DataFolderPath string // Path towards data folder, for tests internal usage, not loading out of .json options
sureTaxCfg *SureTaxCfg // Load here SureTax configuration, as pointer so we can have runtime reloads in the future
ConfigReloads map[string]chan struct{} // Signals to specific entities that a config reload should occur
// Cache defaults loaded from json and needing clones
dfltCdreProfile *CdreConfig // Default cdreConfig profile
@@ -804,35 +805,22 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error {
}
}
if jsnSureTaxCfg != nil {
self.SureTaxCfg = new(SureTaxCfg) // Reset previous values
if jsnSureTaxCfg.Url != nil {
self.SureTaxCfg.Url = *jsnSureTaxCfg.Url
if jsnSureTaxCfg != nil { // New config for SureTax
if self.sureTaxCfg, err = NewSureTaxCfgWithDefaults(); err != nil {
return err
}
if jsnSureTaxCfg.Client_number != nil {
self.SureTaxCfg.ClientNumber = *jsnSureTaxCfg.Client_number
}
if jsnSureTaxCfg.Validation_key != nil {
self.SureTaxCfg.ValidationKey = *jsnSureTaxCfg.Validation_key
}
if jsnSureTaxCfg.Timezone != nil {
if self.SureTaxCfg.Timezone, err = time.LoadLocation(*jsnSureTaxCfg.Timezone); err != nil {
return err
}
}
if jsnSureTaxCfg.Include_local_cost != nil {
self.SureTaxCfg.IncludeLocalCost = *jsnSureTaxCfg.Include_local_cost
}
if jsnSureTaxCfg.Origination_number != nil {
if self.SureTaxCfg.OriginationNumber, err = utils.ParseRSRFields(*jsnSureTaxCfg.Origination_number, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnSureTaxCfg.Termination_number != nil {
if self.SureTaxCfg.TerminationNumber, err = utils.ParseRSRFields(*jsnSureTaxCfg.Termination_number, utils.INFIELD_SEP); err != nil {
return err
}
if err := self.sureTaxCfg.loadFromJsonCfg(jsnSureTaxCfg); err != nil {
return err
}
}
return nil
}
// Use locking to retrieve the configuration, possibility later for runtime reload
func (self *CGRConfig) SureTaxCfg() *SureTaxCfg {
cfgChan := <-self.ConfigReloads[utils.SURETAX] // Lock config for read or reloads
stCfg := self.sureTaxCfg
self.ConfigReloads[utils.SURETAX] <- cfgChan // unlock config for reloads or read
return stCfg
}

View File

@@ -280,13 +280,31 @@ const CGRATES_CFG_JSON = `
"suretax": {
"url": "", // API url
"client_number": "", // client number, provided by SureTax
"validation_key": "", // validation key provided by SureTax
"timezone": "Local", // convert the time of the events to this timezone before sending request out <UTC|Local|$IANA_TZ_DB>
"include_local_cost": false, // sum local calculated cost with tax one in final cost
"origination_number": "Subject", // template extracting origination number out of StoredCdr
"termination_number": "Destination", // template extracting termination number out of StoredCdr
"url": "", // API url
"client_number": "", // client number, provided by SureTax
"validation_key": "", // validation key provided by SureTax
"timezone": "Local", // convert the time of the events to this timezone before sending request out <UTC|Local|$IANA_TZ_DB>
"include_local_cost": false, // sum local calculated cost with tax one in final cost
"return_file_code": "0", // default or Quote purposes <0|Q>
"response_group": "03", // determines how taxes are grouped for the response <03|13>
"response_type": "D4", // determines the granularity of taxes and (optionally) the decimal precision for the tax calculations and amounts in the response
"regulatory_code": "03", // provider type
"client_tracking": "CgrId", // template extracting client information out of StoredCdr; <$RSRFields>
"customer_number": "CustomerNumber", // template extracting customer number out of StoredCdr; <$RSRFields>
"orig_number": "Subject", // template extracting origination number out of StoredCdr; <$RSRFields>
"term_number": "Destination", // template extracting termination number out of StoredCdr; <$RSRFields>
"bill_to_number": "", // template extracting billed to number out of StoredCdr; <$RSRFields>
"zipcode": "", // template extracting billing zip code out of StoredCdr; <$RSRFields>
"plus4": "", // template extracting billing zip code extension out of StoredCdr; <$RSRFields>
"p2pzipcode": "", // template extracting secondary zip code out of StoredCdr; <$RSRFields>
"p2pplus4": "", // template extracting secondary zip code extension out of StoredCdr; <$RSRFields>
"units": "^1", // template extracting number of “lines” or unique charges contained within the revenue out of StoredCdr; <$RSRFields>
"unit_type": "^00", // template extracting number of unique access lines out of StoredCdr; <$RSRFields>
"tax_included": "^0", // template extracting tax included in revenue out of StoredCdr; <$RSRFields>
"tax_situs_rule": "^04", // template extracting tax situs rule out of StoredCdr; <$RSRFields>
"trans_type_code": "^010101", // template extracting transaction type indicator out of StoredCdr; <$RSRFields>
"sales_type_code": "^R", // template extracting sales type code out of StoredCdr; <$RSRFields>
"tax_exemption_code_list": "", // template extracting tax exemption code list out of StoredCdr; <$RSRFields>
},
}`

View File

@@ -459,13 +459,31 @@ func TestDfMailerJsonCfg(t *testing.T) {
func TestDfSureTaxJsonCfg(t *testing.T) {
eCfg := &SureTaxJsonCfg{
Url: utils.StringPointer(""),
Client_number: utils.StringPointer(""),
Validation_key: utils.StringPointer(""),
Timezone: utils.StringPointer("Local"),
Include_local_cost: utils.BoolPointer(false),
Origination_number: utils.StringPointer("Subject"),
Termination_number: utils.StringPointer("Destination"),
Url: utils.StringPointer(""),
Client_number: utils.StringPointer(""),
Validation_key: utils.StringPointer(""),
Timezone: utils.StringPointer("Local"),
Include_local_cost: utils.BoolPointer(false),
Return_file_code: utils.StringPointer("0"),
Response_group: utils.StringPointer("03"),
Response_type: utils.StringPointer("D4"),
Regulatory_code: utils.StringPointer("03"),
Client_tracking: utils.StringPointer("CgrId"),
Customer_number: utils.StringPointer("CustomerNumber"),
Orig_number: utils.StringPointer("Subject"),
Term_number: utils.StringPointer("Destination"),
Bill_to_number: utils.StringPointer(""),
Zipcode: utils.StringPointer(""),
Plus4: utils.StringPointer(""),
P2PZipcode: utils.StringPointer(""),
P2PPlus4: utils.StringPointer(""),
Units: utils.StringPointer("^1"),
Unit_type: utils.StringPointer("^00"),
Tax_included: utils.StringPointer("^0"),
Tax_situs_rule: utils.StringPointer("^04"),
Trans_type_code: utils.StringPointer("^010101"),
Sales_type_code: utils.StringPointer("^R"),
Tax_exemption_code_list: utils.StringPointer(""),
}
if cfg, err := dfCgrJsonCfg.SureTaxJsonCfg(); err != nil {
t.Error(err)

View File

@@ -22,7 +22,6 @@ import (
"fmt"
"github.com/cgrates/cgrates/utils"
"net/url"
"time"
)
type CdrReplicationCfg struct {
@@ -36,13 +35,3 @@ type CdrReplicationCfg struct {
func (rplCfg CdrReplicationCfg) FallbackFileName() string {
return fmt.Sprintf("cdr_%s_%s_%s.form", rplCfg.Transport, url.QueryEscape(rplCfg.Server), utils.GenUUID())
}
type SureTaxCfg struct {
Url string
ClientNumber string
ValidationKey string
Timezone *time.Location // Convert the time of the events to this timezone before sending request out
IncludeLocalCost bool
OriginationNumber utils.RSRFields // Concatenate all of them to get value
TerminationNumber utils.RSRFields // Concatenate all of them to get value
}

View File

@@ -256,11 +256,29 @@ type MailerJsonCfg struct {
// SureTax config section
type SureTaxJsonCfg struct {
Url *string // API url
Client_number *string // client number provided by SureTax
Validation_key *string // validation key provided by SureTax
Timezone *string // convert the time of the events to this timezone before sending request out
Include_local_cost *bool // sum local calculated cost with tax one
Origination_number *string // template extracting origination number out of StoredCdr
Termination_number *string // template extracting origination number out of StoredCdr
Url *string
Client_number *string
Validation_key *string
Timezone *string
Include_local_cost *bool
Return_file_code *string
Response_group *string
Response_type *string
Regulatory_code *string
Client_tracking *string
Customer_number *string
Orig_number *string
Term_number *string
Bill_to_number *string
Zipcode *string
Plus4 *string
P2PZipcode *string
P2PPlus4 *string
Units *string
Unit_type *string
Tax_included *string
Tax_situs_rule *string
Trans_type_code *string
Sales_type_code *string
Tax_exemption_code_list *string
}

186
config/suretaxconfig.go Normal file
View File

@@ -0,0 +1,186 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"github.com/cgrates/cgrates/utils"
"strings"
"time"
)
// Creates a new SureTaxCfg with defaults pre-populated out of config_defaults.json
func NewSureTaxCfgWithDefaults() (*SureTaxCfg, error) {
jsnCfg, err := NewCgrJsonCfgFromReader(strings.NewReader(CGRATES_CFG_JSON))
if err != nil {
return nil, err
}
jsnSureTaxCfg, err := jsnCfg.SureTaxJsonCfg()
if err != nil {
return nil, err
}
st := new(SureTaxCfg)
if err := st.loadFromJsonCfg(jsnSureTaxCfg); err != nil {
return nil, err
}
return st, nil
}
// SureTax configuration object
type SureTaxCfg struct {
Url string
ClientNumber string
ValidationKey string
Timezone *time.Location // Convert the time of the events to this timezone before sending request out
IncludeLocalCost bool
ReturnFileCode string
ResponseGroup string
ResponseType string
RegulatoryCode string
ClientTracking utils.RSRFields // Concatenate all of them to get value
CustomerNumber utils.RSRFields
OrigNumber utils.RSRFields
TermNumber utils.RSRFields
BillToNumber utils.RSRFields
Zipcode utils.RSRFields
Plus4 utils.RSRFields
P2PZipcode utils.RSRFields
P2PPlus4 utils.RSRFields
Units utils.RSRFields
UnitType utils.RSRFields
TaxIncluded utils.RSRFields
TaxSitusRule utils.RSRFields
TransTypeCode utils.RSRFields
SalesTypeCode utils.RSRFields
TaxExemptionCodeList utils.RSRFields
}
// Loads/re-loads data from json config object
func (self *SureTaxCfg) loadFromJsonCfg(jsnCfg *SureTaxJsonCfg) error {
var err error
if jsnCfg.Url != nil {
self.Url = *jsnCfg.Url
}
if jsnCfg.Client_number != nil {
self.ClientNumber = *jsnCfg.Client_number
}
if jsnCfg.Validation_key != nil {
self.ValidationKey = *jsnCfg.Validation_key
}
if jsnCfg.Timezone != nil {
if self.Timezone, err = time.LoadLocation(*jsnCfg.Timezone); err != nil {
return err
}
}
if jsnCfg.Include_local_cost != nil {
self.IncludeLocalCost = *jsnCfg.Include_local_cost
}
if jsnCfg.Return_file_code != nil {
self.ReturnFileCode = *jsnCfg.Return_file_code
}
if jsnCfg.Response_group != nil {
self.ResponseGroup = *jsnCfg.Response_group
}
if jsnCfg.Response_type != nil {
self.ResponseType = *jsnCfg.Response_type
}
if jsnCfg.Regulatory_code != nil {
self.RegulatoryCode = *jsnCfg.Regulatory_code
}
if jsnCfg.Client_tracking != nil {
if self.ClientTracking, err = utils.ParseRSRFields(*jsnCfg.Client_tracking, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Customer_number != nil {
if self.CustomerNumber, err = utils.ParseRSRFields(*jsnCfg.Customer_number, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Orig_number != nil {
if self.OrigNumber, err = utils.ParseRSRFields(*jsnCfg.Orig_number, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Term_number != nil {
if self.TermNumber, err = utils.ParseRSRFields(*jsnCfg.Term_number, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Bill_to_number != nil {
if self.BillToNumber, err = utils.ParseRSRFields(*jsnCfg.Bill_to_number, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Zipcode != nil {
if self.Zipcode, err = utils.ParseRSRFields(*jsnCfg.Zipcode, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Plus4 != nil {
if self.Plus4, err = utils.ParseRSRFields(*jsnCfg.Plus4, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.P2PZipcode != nil {
if self.P2PZipcode, err = utils.ParseRSRFields(*jsnCfg.P2PZipcode, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.P2PPlus4 != nil {
if self.P2PPlus4, err = utils.ParseRSRFields(*jsnCfg.P2PPlus4, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Units != nil {
if self.Units, err = utils.ParseRSRFields(*jsnCfg.Units, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Unit_type != nil {
if self.UnitType, err = utils.ParseRSRFields(*jsnCfg.Unit_type, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Tax_included != nil {
if self.TaxIncluded, err = utils.ParseRSRFields(*jsnCfg.Tax_included, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Tax_situs_rule != nil {
if self.TaxSitusRule, err = utils.ParseRSRFields(*jsnCfg.Tax_situs_rule, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Trans_type_code != nil {
if self.TransTypeCode, err = utils.ParseRSRFields(*jsnCfg.Trans_type_code, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Sales_type_code != nil {
if self.SalesTypeCode, err = utils.ParseRSRFields(*jsnCfg.Sales_type_code, utils.INFIELD_SEP); err != nil {
return err
}
}
if jsnCfg.Tax_exemption_code_list != nil {
if self.TaxExemptionCodeList, err = utils.ParseRSRFields(*jsnCfg.Tax_exemption_code_list, utils.INFIELD_SEP); err != nil {
return err
}
}
return nil
}

View File

@@ -19,14 +19,89 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package engine
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"time"
"strings"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
var sureTaxClient *http.Client // Cache the client here if in use
// Init a new request to be sent out to SureTax
func NewSureTaxRequest(cdr *StoredCdr, stCfg *config.SureTaxCfg) (*SureTaxRequest, error) {
aTimeLoc := cdr.AnswerTime.In(stCfg.Timezone)
revenue := utils.Round(cdr.Cost, 4, utils.ROUNDING_MIDDLE)
unts, err := strconv.ParseInt(cdr.FieldsAsString(stCfg.Units), 10, 64)
if err != nil {
return nil, err
}
taxExempt := []string{}
definedTaxExtempt := cdr.FieldsAsString(stCfg.TaxExemptionCodeList)
if len(definedTaxExtempt) != 0 {
taxExempt = strings.Split(cdr.FieldsAsString(stCfg.TaxExemptionCodeList), ",")
}
stReq := new(SureTaxRequest)
stReq.ClientNumber = stCfg.ClientNumber
stReq.BusinessUnit = "" // Export it to config
stReq.ValidationKey = stCfg.ValidationKey
stReq.DataYear = strconv.Itoa(aTimeLoc.Year())
stReq.DataMonth = strconv.Itoa(int(aTimeLoc.Month()))
stReq.TotalRevenue = revenue
stReq.ReturnFileCode = stCfg.ReturnFileCode
stReq.ClientTracking = cdr.FieldsAsString(stCfg.ClientTracking)
stReq.ResponseGroup = stCfg.ResponseGroup
stReq.ResponseType = stCfg.ResponseType
stReq.ItemList = []*STRequestItem{
&STRequestItem{
CustomerNumber: cdr.FieldsAsString(stCfg.CustomerNumber),
OrigNumber: cdr.FieldsAsString(stCfg.OrigNumber),
TermNumber: cdr.FieldsAsString(stCfg.TermNumber),
BillToNumber: cdr.FieldsAsString(stCfg.BillToNumber),
Zipcode: cdr.FieldsAsString(stCfg.Zipcode),
Plus4: cdr.FieldsAsString(stCfg.Plus4),
P2PZipcode: cdr.FieldsAsString(stCfg.P2PZipcode),
P2PPlus4: cdr.FieldsAsString(stCfg.P2PPlus4),
TransDate: aTimeLoc.Format("2006-01-02T15:04:05"),
Revenue: revenue,
Units: unts,
UnitType: cdr.FieldsAsString(stCfg.UnitType),
Seconds: int64(cdr.Usage.Seconds()),
TaxIncludedCode: cdr.FieldsAsString(stCfg.TaxIncluded),
TaxSitusRule: cdr.FieldsAsString(stCfg.TaxSitusRule),
TransTypeCode: cdr.FieldsAsString(stCfg.TransTypeCode),
SalesTypeCode: cdr.FieldsAsString(stCfg.SalesTypeCode),
RegulatoryCode: stCfg.RegulatoryCode,
TaxExemptionCodeList: taxExempt,
},
}
return stReq, nil
}
// SureTax Request type
type SureTaxRequest struct {
ClientNumber string // Client ID Number provided by SureTax. Required. Max Len: 10
BusinessUnit string // Clients Business Unit. Value for this field is not required. Max Len: 20
ValidationKey string // Validation Key provided by SureTax. Required for client access to API function. Max Len: 36
DataYear string // Required. YYYY Year to use for tax calculation purposes
DataMonth string // Required. MM Month to use for tax calculation purposes. Leading zero is preferred.
TotalRevenue float64 // Required. Format: $$$$$$$$$.CCCC. For Negative charges, the first position should have a minus - indicator.
ReturnFileCode string // Required. 0 Default.Q Quote purposes taxes are computed and returned in the response message for generating quotes.
ClientTracking string // Field for client transaction tracking. This value will be provided in the response data. Value for this field is not required, but preferred. Max Len: 100
IndustryExemption string // Reserved for future use.
ResponseGroup string // Required. Determines how taxes are grouped for the response.
ResponseType string // Required. Determines the granularity of taxes and (optionally) the decimal precision for the tax calculations and amounts in the response.
ItemList []*STRequestItem // List of Item records
}
// Part of SureTax Request
type STRequestItem struct {
LineNumber string // Used to identify an item within the request. If no value is provided, requests are numbered sequentially. Max Len: 40
@@ -52,62 +127,6 @@ type STRequestItem struct {
TaxExemptionCodeList []string // Required. Tax Exemption to be applied to this item only.
}
// Init a new request to be sent out to SureTax
func NewSureTaxRequest(clientNumber, validationKey string, timezone *time.Location, originationNrTpl, terminationNrTpl utils.RSRFields, cdr *StoredCdr) (*SureTaxRequest, error) {
if clientNumber == "" {
return nil, utils.NewErrMandatoryIeMissing("ClientNumber")
}
if validationKey == "" {
return nil, utils.NewErrMandatoryIeMissing("ValidationKey")
}
aTime := cdr.AnswerTime.In(timezone)
stReq := &SureTaxRequest{ClientNumber: clientNumber,
ValidationKey: validationKey,
DataYear: strconv.Itoa(aTime.Year()),
DataMonth: strconv.Itoa(int(aTime.Month())),
TotalRevenue: utils.Round(cdr.Cost, 4, utils.ROUNDING_MIDDLE),
ReturnFileCode: "0",
ClientTracking: cdr.CgrId,
ResponseGroup: "03",
ResponseType: "",
ItemList: []*STRequestItem{
&STRequestItem{
OrigNumber: cdr.FieldsAsString(originationNrTpl),
TermNumber: cdr.FieldsAsString(terminationNrTpl),
BillToNumber: cdr.FieldsAsString(originationNrTpl),
TransDate: aTime.Format("2006-01-02T15:04:05"),
Revenue: utils.Round(cdr.Cost, 4, utils.ROUNDING_MIDDLE),
Units: 1,
UnitType: "00",
Seconds: int64(utils.Round(cdr.Usage.Seconds(), 0, utils.ROUNDING_MIDDLE)),
TaxIncludedCode: "0",
TaxSitusRule: "1",
TransTypeCode: "010101",
SalesTypeCode: "R",
RegulatoryCode: "01",
TaxExemptionCodeList: []string{"00"},
},
},
}
return stReq, nil
}
// SureTax Request type
type SureTaxRequest struct {
ClientNumber string // Client ID Number provided by SureTax. Required. Max Len: 10
BusinessUnit string // Clients Business Unit. Value for this field is not required. Max Len: 20
ValidationKey string // Validation Key provided by SureTax. Required for client access to API function. Max Len: 36
DataYear string // Required. YYYY Year to use for tax calculation purposes
DataMonth string // Required. MM Month to use for tax calculation purposes. Leading zero is preferred.
TotalRevenue float64 // Required. Format: $$$$$$$$$.CCCC. For Negative charges, the first position should have a minus - indicator.
ReturnFileCode string // Required. 0 Default.Q Quote purposes taxes are computed and returned in the response message for generating quotes.
ClientTracking string // Field for client transaction tracking. This value will be provided in the response data. Value for this field is not required, but preferred. Max Len: 100
IndustryExemption string // Reserved for future use.
ResponseGroup string // Required. Determines how taxes are grouped for the response.
ResponseType string // Required. Determines the granularity of taxes and (optionally) the decimal precision for the tax calculations and amounts in the response.
ItemList []*STRequestItem // List of Item records
}
// Converts the request into the format SureTax expects
func (self *SureTaxRequest) AsHttpForm() (url.Values, error) {
jsnContent, err := json.Marshal(self)
@@ -122,7 +141,7 @@ func (self *SureTaxRequest) AsHttpForm() (url.Values, error) {
// SureTax Response type
type SureTaxResponse struct {
Successful string // Response will be either Y' or N' : Y = Success / Success with Item error N = Failure
ResponseCode string // ResponseCode: 9999 Request was successful. 1101-1400 Range of values for a failed request (no processing occurred) 9001 Request was successful, but items within the request have errors. The specific items with errors are provided in the ItemMessages field.
ResponseCode int64 // ResponseCode: 9999 Request was successful. 1101-1400 Range of values for a failed request (no processing occurred) 9001 Request was successful, but items within the request have errors. The specific items with errors are provided in the ItemMessages field.
HeaderMessage string // Response message: For ResponseCode 9999 “Success”For ResponseCode 9001 “Success with Item errors”. For ResponseCode 1100-1400 Unsuccessful / declined web request.
ItemMessages []*STItemMessage // This field contains a list of items that were not able to be processed due to bad or invalid data (see Response Code of “9001”).
ClientTracking string // Client transaction tracking provided in web request.
@@ -152,3 +171,53 @@ type STTaxItem struct {
TaxTypeDesc string // Tax Type Description
TaxAmount float64 // Tax Amount
}
func SureTaxProcessCdr(cdr *StoredCdr) error {
stCfg := config.CgrConfig().SureTaxCfg()
if stCfg == nil {
return errors.New("SureTax configuration missing")
}
if sureTaxClient == nil { // First time used, init the client here
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: config.CgrConfig().HttpSkipTlsVerify},
}
sureTaxClient = &http.Client{Transport: tr}
}
req, err := NewSureTaxRequest(cdr, stCfg)
if err != nil {
return err
}
body, err := json.Marshal(req)
if err != nil {
return err
}
resp, err := sureTaxClient.Post(stCfg.Url, "application/json", bytes.NewBuffer(body))
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode > 299 {
return fmt.Errorf("Unexpected status code received: %d", resp.StatusCode)
}
var stResp SureTaxResponse
if err := json.Unmarshal(respBody, &stResp); err != nil {
return err
}
if stResp.ResponseCode != 9999 {
cdr.ExtraInfo = stResp.HeaderMessage
return nil // No error because the request was processed by SureTax, error will be in the ExtraInfo
}
// Write cost to CDR
if !stCfg.IncludeLocalCost {
cdr.Cost = utils.Round(stResp.TotalTax, config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)
} else {
cdr.Cost = utils.Round(cdr.Cost+stResp.TotalTax, config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)
}
// Add response into extra fields to be available for later review
cdr.ExtraFields[utils.META_SURETAX] = string(respBody)
return nil
}

View File

@@ -28,47 +28,48 @@ import (
)
func TestNewSureTaxRequest(t *testing.T) {
storedCdr := &StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE,
cgrId := utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String())
cdr := &StoredCdr{CgrId: cgrId, OrderId: 123, TOR: utils.VOICE,
AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out",
Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID,
Usage: time.Duration(12) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans", Rated: true,
}
cfg, _ := config.NewDefaultCGRConfig()
cfg.SureTaxCfg.ClientNumber = "000000000"
cfg.SureTaxCfg.ValidationKey = "19491161-F004-4F44-BDB3-E976D6739A64"
cfg.SureTaxCfg.Timezone = time.UTC
stCfg := cfg.SureTaxCfg()
stCfg.ClientNumber = "000000000"
stCfg.ValidationKey = "19491161-F004-4F44-BDB3-E976D6739A64"
stCfg.Timezone = time.UTC
eSureTaxRequest := &SureTaxRequest{
ClientNumber: cfg.SureTaxCfg.ClientNumber,
ValidationKey: cfg.SureTaxCfg.ValidationKey,
ClientNumber: "000000000",
ValidationKey: "19491161-F004-4F44-BDB3-E976D6739A64",
DataYear: "2013",
DataMonth: "11",
TotalRevenue: 1.01,
ReturnFileCode: "0",
ClientTracking: storedCdr.CgrId,
ClientTracking: cgrId,
ResponseGroup: "03",
ResponseType: "",
ResponseType: "D4",
ItemList: []*STRequestItem{
&STRequestItem{
OrigNumber: "1001",
TermNumber: "1002",
BillToNumber: "1001",
BillToNumber: "",
TransDate: "2013-11-07T08:42:26",
Revenue: 1.01,
Units: 1,
UnitType: "00",
Seconds: 12,
TaxIncludedCode: "0",
TaxSitusRule: "1",
TaxSitusRule: "04",
TransTypeCode: "010101",
SalesTypeCode: "R",
RegulatoryCode: "01",
TaxExemptionCodeList: []string{"00"},
RegulatoryCode: "03",
TaxExemptionCodeList: []string{},
},
},
}
if stReq, err := NewSureTaxRequest(cfg.SureTaxCfg.ClientNumber, cfg.SureTaxCfg.ValidationKey, cfg.SureTaxCfg.Timezone, cfg.SureTaxCfg.OriginationNumber,
cfg.SureTaxCfg.TerminationNumber, storedCdr); err != nil {
if stReq, err := NewSureTaxRequest(cdr, stCfg); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eSureTaxRequest, stReq) {
t.Errorf("Expecting: %+v, received: %+v", eSureTaxRequest.ItemList[0], stReq.ItemList[0])

View File

@@ -235,6 +235,8 @@ const (
NOT_AVAILABLE = "N/A"
CALL = "call"
EXTRA_FIELDS = "ExtraFields"
META_SURETAX = "*sure_tax"
SURETAX = "suretax"
)
var (