/* Real-time Online/Offline Charging System (OCS) 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 Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see */ package engine import ( "bytes" "encoding/json" "errors" "fmt" "io" "net/http" "strconv" "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 *CDR, stCfg *config.SureTaxCfg) (*SureTaxRequest, error) { if stCfg == nil { return nil, errors.New("invalid SureTax config") } aTimeLoc := cdr.AnswerTime.In(stCfg.Timezone) revenue := utils.Round(cdr.Cost, 4, utils.MetaRoundingMiddle) 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(STRequest) stReq.ClientNumber = stCfg.ClientNumber stReq.BusinessUnit = stCfg.BusinessUnit 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{ { 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, }, } jsnContent, err := json.Marshal(stReq) if err != nil { return nil, err } return &SureTaxRequest{Request: string(jsnContent)}, nil } // SureTax JSON Request type SureTaxRequest struct { Request string `json:"request"` // SureTax Requires us to encapsulate the content into a request element } // SureTax JSON Response type SureTaxResponse struct { D string // SureTax requires encapsulating reply into a D object } // SureTax Request type type STRequest struct { ClientNumber string // Client ID Number – provided by SureTax. Required. Max Len: 10 BusinessUnit string // Client’s 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 InvoiceNumber string // Used for tax aggregation by Invoice. Must be alphanumeric. Max Len: 40 CustomerNumber string // Used for tax aggregation by Customer. Must be alphanumeric. Max Len: 40 OrigNumber string // Required when using Tax Situs Rule 01 or 03. Format: NPANXXNNNN TermNumber string // Required when using Tax Situs Rule 01. Format: NPANXXNNNN BillToNumber string // Required when using Tax Situs Rule 01 or 02. Format: NPANXXNNNN Zipcode string // Required when using Tax Situs Rule 04, 05, or 14. Plus4 string // Zip code extension in format: 9999 (not applicable for Tax Situs Rule 14) P2PZipcode string // Secondary zip code in format: 99999 (US or US territory) or X9X9X9 (Canadian) P2PPlus4 string // Secondary zip code extension in format: 99999 (US or US territory) or X9X9X9 (Canadian) TransDate string // Required. Date of transaction. Valid date formats include: MM/DD/YYYY, MM-DD-YYYY, YYYY-MM-DDTHH:MM:SS Revenue float64 // Required. Format: $$$$$$$$$.CCCC. For Negative charges, the first position should have a minus ‘-‘indicator. Units int64 // Required. Units representing number of “lines” or unique charges contained within the revenue. This value is essentially a multiplier on unit-based fees (e.g. E911 fees). Format: 99999. Default should be 1 (one unit). UnitType string // Required. 00 – Default / Number of unique access lines. Seconds int64 // Required. Duration of call in seconds. Format 99999. Default should be 1. TaxIncludedCode string // Required. Values: 0 – Default (No Tax Included) 1 – Tax Included in Revenue TaxSitusRule string // Required. TransTypeCode string // Required. Transaction Type Indicator. SalesTypeCode string // Required. Values: R – Residential customer (default) B – Business customer I – Industrial customer L – Lifeline customer RegulatoryCode string // Required. Provider Type. TaxExemptionCodeList []string // Required. Tax Exemption to be applied to this item only. } type STResponse 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. 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. TotalTax string // Total Tax – a total of all taxes included in the TaxList TransId int // Transaction ID – provided by SureTax GroupList []*STGroup // contains one-to-many Groups } // Part of the SureTax Response type STItemMessage struct { LineNumber string // value corresponding to the line number in the web request ResponseCode string // a value in the range 9100-9400 Message string // the error message corresponding to the ResponseCode } // Part of the SureTax Response type STGroup struct { StateCode string // Tax State InvoiceNumber string // Invoice Number CustomerNumber string // Customer number TaxList []*STTaxItem // contains one-to-many Tax Items } // Part of the SureTax Response type STTaxItem struct { TaxTypeCode string // Tax Type Code TaxTypeDesc string // Tax Type Description TaxAmount string // Tax Amount } func SureTaxProcessCdr(cdr *CDR) error { stCfg := config.CgrConfig().SureTaxCfg() if stCfg == nil { return errors.New("Invalid SureTax configuration") } if sureTaxClient == nil { // First time used, init the client here sureTaxClient = &http.Client{ Transport: httpPstrTransport, } } req, err := NewSureTaxRequest(cdr, stCfg) if err != nil { return err } jsnContent, err := json.Marshal(req) if err != nil { return err } resp, err := sureTaxClient.Post(stCfg.URL, "application/json", bytes.NewBuffer(jsnContent)) if err != nil { return err } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return err } if resp.StatusCode > 299 { return fmt.Errorf("Unexpected status code received: %d", resp.StatusCode) } var respFull SureTaxResponse if err := json.Unmarshal(respBody, &respFull); err != nil { return err } var stResp STResponse if err := json.Unmarshal([]byte(respFull.D), &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 totalTax, err := strconv.ParseFloat(stResp.TotalTax, 64) if err != nil { cdr.ExtraInfo = err.Error() } if !stCfg.IncludeLocalCost { cdr.Cost = utils.Round(totalTax, config.CgrConfig().GeneralCfg().RoundingDecimals, utils.MetaRoundingMiddle) } else { cdr.Cost = utils.Round(cdr.Cost+totalTax, config.CgrConfig().GeneralCfg().RoundingDecimals, utils.MetaRoundingMiddle) } // Add response into extra fields to be available for later review cdr.ExtraFields[utils.MetaSureTax] = respFull.D return nil }