Files
cgrates/engine/suretax.go
2015-10-27 19:00:26 +01:00

228 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2012-2015 ITsysCOM
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 engine
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"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 *StoredCdr, 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.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(STRequest)
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 &SureTaxRequest{Request: stReq}, nil
}
type SureTaxRequest struct {
Request *STRequest // SureTax Requires us to encapsulate the content into a request element
}
// SureTax Request type
type STRequest 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
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.
}
// SureTax Response type
type SureTaxResponse struct {
D *STResponse // SureTax requires encapsulating reply into a D object
}
type STResponse struct {
Successful string // Response will be either Y' or N' : Y = Success / Success with Item error N = Failure
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.
TotalTax float64 // 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 float64 // Tax Amount
}
func SureTaxProcessCdr(cdr *StoredCdr) error {
stCfg := config.CgrConfig().SureTaxCfg()
if stCfg == nil {
return errors.New("Invalid SureTax configuration")
}
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
}
jsnContent, err := json.Marshal(req)
if err != nil {
return err
}
utils.Logger.Debug(fmt.Sprintf("NewSureTaxRequest: %s\n", string(jsnContent)))
resp, err := sureTaxClient.Post(stCfg.Url, "application/json", bytes.NewBuffer(jsnContent))
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
utils.Logger.Debug(fmt.Sprintf("Unexpected response body received, error: %s\n", err.Error()))
return err
}
if resp.StatusCode > 299 {
utils.Logger.Debug(fmt.Sprintf("Unexpected code received: %d\n", resp.StatusCode))
return fmt.Errorf("Unexpected status code received: %d", resp.StatusCode)
}
utils.Logger.Debug(fmt.Sprintf("Received raw answer from SureTax: %s\n", string(respBody)))
var stResp SureTaxResponse
if err := json.Unmarshal(respBody, &stResp); err != nil {
return err
}
utils.Logger.Debug(fmt.Sprintf("Received answer from SureTax: %+v\n", stResp))
if stResp.D.ResponseCode != 9999 {
cdr.ExtraInfo = stResp.D.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.D.TotalTax, config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)
} else {
cdr.Cost = utils.Round(cdr.Cost+stResp.D.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
}