Creating a Custom Plug-in Implementation for a Tax Authority

Note:

To access the LATAM E-Document Certification SuiteApp documentation in Brazilian Portuguese (Português do Brasil), see LATAM E-Document Certification.

A custom plug-in implementation for a tax authority contains all the methods, parameters, and templates required to connect NetSuite to the tax authority web services. The implementation also handles the messages returned by the tax authority.

Create a JavaScript file for the custom plug-in implementation. The JavaScript file must be compatible with SuiteScript 2.0.

The following code is a sample custom plug-in implementation for tax authority certification service.

          /**
 *
 * @NApiVersion 2.1
 * @NModuleScope Public
 * @NScriptType plugintypeimpl
 */
 
define([
    "N/xml",
    "N/search",
    "SuiteApps/com.netsuite.edoccertificationservice/src/ecsShared/lib/taxAuthorityPluginLib"
], function(
    xml,
    search,
    taxAuthLib
) {
    /**
     * Build a object that contains a headers and a Soap Envelope with XML Content
     * @param {Object} context
     * @param {string|number} context.subsidiary
     * @param {string|number} context.taxAuthority
     * @param {string|number} context.environment
     * @param {string|number} context.operationId
     * @param {string|number} context.operationType
     * @param {string|number} context.operationTypeExternalId
     * @param {string|number} context.recordId
     * @param {string|number} context.recordType
     * @param {string|number} context.eDocContent
     * @param {any} context.requestParameters
     *
     * @returns {Object} result
     * @returns {any} result.headers
     * @returns {string} result.contents
    */
    function buildMessage(context) {   
        var requestParameters = context.requestParameters;
        var requestType = context.requestParameters.requestType;
        var xmlContent = null;
 
        if (requestType == "ConsultarLoteRpsRequest") {
            var protocolNumber = requestParameters.certificationProtocol;
            var subsidiaryCnpj = requestParameters.subsidiaryCnpj;
            var subsidiaryInscricaoMunicipal = requestParameters.subsidiaryInscricaoMunicipal;

            xmlContent = getRenderedCheckStatusTemplate({
                taxAuthority: context.taxAuthority,
                operationId: context.requestParameters.nextOperation,
                protocol: protocolNumber,
                subsidiary: context.subsidiary.toString(),
                subsidiaryCnpj: subsidiaryCnpj,
                subsidiaryInscricaoMunicipal: subsidiaryInscricaoMunicipal
            });
        } else {
            var xmlSoap = taxAuthLib.getXmlSoap(context.operationId);
            xmlContent = context.eDocContent.replace('<?xml version="1.0" encoding="UTF-8" standalone="no"?>', "");
            xmlContent = xmlSoap.replace("#XMLDATA#", xmlContent);
    
            break;
        }
 
        return {
            headers: {'Content-Type': 'text/xml;charset=utf-8'},
            contents: xmlContent
        };
    };
 
    /**
     * Read content response that returns from the Tax Authority Web Service
     * @param {Object} context
     * @param {string|number} context.subsidiary
     * @param {string|number} context.taxAuthority
     * @param {string|number} context.environment
     * @param {string|number} context.category
     * @param {string|number} context.operationId
     * @param {string|number} context.operationType
     * @param {string|number} context.operationTypeExternalId
     * @param {string|number} context.recordId
     * @param {string|number} context.recordType
     * @param {string|number} context.eDocContent
     * @param {any} context.requestParameters
     * @param {Object} context.response
     * @param {string} context.response.body
     * @param {code} context.response.code
     * @param {headers} context.response.headers
     *
     * @returns {Object} result
     * @returns {boolean} result.success
     * @returns {string} result.message
     * @returns {string} result.contents
     * @returns {any} result.details
    */
    function readResponse(context) {
        var requestType = context.requestParameters.requestType;
 
        if (!requestType) {
            var operationTypeExternalId = context.operationTypeExternalId;
 
            if (operationTypeExternalId === "Send E-Document") {
                requestType = "RecepcionarLoteRpsRequest";
            }        
            if (operationTypeExternalId === "Cancel") {
                requestType = "CancelarNfseRequest";
            }
        }
 
        var xmlParse = parseXml(context.response.body);
        var success = false;
 
        var responseContents = xml.XPath.select({
            node: xmlParse,
            xpath: '//return'
        })[0].textContent;
 
        var responseParsedXml = parseXml(responseContents);
        var originalParsedXml = parseXml(context.eDocContent);
 
        var details = null;
        var certificationProtocol = "";
        var messageCode = "";   
        var message = "";
        var fixMessage = "";
        var certificationDate = null;
        var documentSeries = "";
        var transitoryDocumentNumber = "";
        var certifiedDocumentNumber = "";
        var accessKey = "";
        var subsidiaryCnpj = "";
        var subsidiaryInscricaoMunicipal = "";
 
        switch(requestType) {
            case "RecepcionarLoteRpsRequest": {
                certificationProtocol = taxAuthLib.getFirstElementByTagName(responseParsedXml, 'ns3:Protocolo');
     
                messageCode = taxAuthLib.getElementByXpath(responseParsedXml, "//*[contains(name(), ':MensagemRetorno')]//*[contains(name(), ':Codigo')]");
                message  = taxAuthLib.getElementByXpath(responseParsedXml, "//*[contains(name(), ':MensagemRetorno')]//*[contains(name(), ':Mensagem')]");
                fixMessage = taxAuthLib.getElementByXpath(responseParsedXml, "//*[contains(name(), ':MensagemRetorno')]//*[contains(name(), ':Correcao')]");
     
                message = taxAuthLib.formatErrorMessage(message, messageCode, fixMessage);
     
                subsidiaryCnpj = taxAuthLib.getFirstElementByTagName(originalParsedXml, 'Cnpj');
                subsidiaryInscricaoMunicipal = taxAuthLib.getFirstElementByTagName(originalParsedXml, 'InscricaoMunicipal');
     
                if (checkSuccessRecepcionarLote(messageCode, certificationProtocol)) {
                    success = true;
                    details = {
                        subsidiaryCnpj: subsidiaryCnpj,
                        subsidiaryInscricaoMunicipal: subsidiaryInscricaoMunicipal,
                        certificationProtocol: certificationProtocol
                    };
                     
                } else {
                    details = {
                        messageCode: messageCode
                    };
                }
     
                break;
            }
            case "ConsultarLoteRpsRequest": {
                messageCode = taxAuthLib.getElementByXpath(responseParsedXml, "//*[contains(name(), ':MensagemRetorno')]//*[contains(name(), ':Codigo')]");
                message  = taxAuthLib.getElementByXpath(responseParsedXml, "//*[contains(name(), ':MensagemRetorno')]//*[contains(name(), ':Mensagem')]");
                fixMessage = taxAuthLib.getElementByXpath(responseParsedXml, "//*[contains(name(), ':MensagemRetorno')]//*[contains(name(), ':Correcao')]");
                 
                message = taxAuthLib.formatErrorMessage(message, messageCode, fixMessage);
                 
                var certificationDateString = taxAuthLib.getFirstElementByTagName(responseParsedXml, 'ns4:DataEmissao');
     
                if (errorCodeToReprocess(messageCode)) {
     
                    success = true;
                    details = {
                        messageCode: messageCode,
                        subsidiaryCnpj: context.requestParameters.subsidiaryCnpj,
                        subsidiaryInscricaoMunicipal: context.requestParameters.subsidiaryInscricaoMunicipal,
                        certificationProtocol: context.requestParameters.certificationProtocol
                    };
     
                } else if (checkSuccessConsultarLote(messageCode, certificationDateString)) {
                    documentSeries = taxAuthLib.getElementByXpath(responseParsedXml, "//*[name()='ns4:IdentificacaoRps']//*[name()='ns4:Serie']");
                    transitoryDocumentNumber = taxAuthLib.getElementByXpath(responseParsedXml, "//*[name()='ns4:IdentificacaoRps']//*[name()='ns4:Numero']");
                    certificationDate = taxAuthLib.parseISODatetime(certificationDateString);
                    certifiedDocumentNumber = taxAuthLib.getElementByXpath(responseParsedXml, "//*[name()='ns4:InfNfse']//*[name()='ns4:Numero']");
                    accessKey = taxAuthLib.getElementByXpath(responseParsedXml, "//*[name()='ns4:InfNfse']//*[name()='ns4:CodigoVerificacao']");
                     
                    success = true;
                    details = {
                        certificationDate: certificationDate,
                        documentSeries: documentSeries,
                        transitoryDocumentNumber: transitoryDocumentNumber,
                        certifiedDocumentNumber: certifiedDocumentNumber,
                        documentAccessKey: accessKey,
                        additionalInformation: {
                            certificationDateString: certificationDateString
                        }
                    };
                } else {
                    success = false;
                    details = {
                        messageCode: messageCode
                    }
                }
                break;
            }
            case "CancelarNfseRequest": {
                messageCode = taxAuthLib.getElementByXpath(responseParsedXml, "//*[contains(name(), ':MensagemRetorno')]//*[contains(name(), ':Codigo')]");
                message  = taxAuthLib.getElementByXpath(responseParsedXml, "//*[contains(name(), ':MensagemRetorno')]//*[contains(name(), ':Mensagem')]");
                fixMessage = taxAuthLib.getElementByXpath(responseParsedXml, "//*[contains(name(), ':MensagemRetorno')]//*[contains(name(), ':Correcao')]");
     
                message = taxAuthLib.formatErrorMessage(message, messageCode, fixMessage);
     
                if (checkSuccessCancelarNfse(messageCode)) {
                    certifiedDocumentNumber = taxAuthLib.getFirstElementByTagName(responseParsedXml, 'ns5:Numero') || taxAuthLib.getFirstElementByTagName(originalParsedXml, 'tns:NumeroNfse');
                    var certificationDateString = taxAuthLib.getFirstElementByTagName(responseParsedXml, 'ns5:DataHora');
                    certificationDate = taxAuthLib.parseISODatetime(certificationDateString);
                    success = true;
     
                    details = {
                        certificationDate: certificationDate,
                        certifiedDocumentNumber: certifiedDocumentNumber,
                        additionalInformation: {
                            certificationDateString: certificationDateString
                        }
                    };
                } else {
                    details = {
                        messageCode: messageCode
                    };
                }
     
                break;
            }
        }
 
 
        return {
            success: success,
            message: message,
            contents: responseContents,
            details: details
        };
    };
 
    /**
     * Verify if the current operation type has a next request
     * @param {Object} context
     * @param {string|number} context.subsidiary
     * @param {string|number} context.taxAuthority
     * @param {string|number} context.environment
     * @param {string|number} context.operationId
     * @param {string|number} context.operationType
     * @param {string|number} context.operationTypeExternalId
     * @param {string|number} context.recordId
     * @param {string|number} context.recordType
     * @param {string|number} context.eDocContent
     * @param {any} context.requestParameters
     * @param {Object} context.responseResult
     * @param {boolean} context.responseResult.success
     * @param {string} context.responseResult.message
     * @param {string} context.responseResult.eDocContent
     * @param {any} context.responseResult.details
     *
     * @returns {Object} result
     * @returns {boolean} result.hasNextRequest
     * @returns {any} result.requestParameters
    */
    function getNextRequest(context) {
        var hasNextRequest = false;
        var requestParameters = null;
 
        if (context.operationTypeExternalId === "Send E-Document") {
 
            var requestType = context.requestParameters.requestType;
            var transaction = context.requestParameters.transaction;
            var resultDocumentSubsidiaryCnpj = context.responseResult.details.subsidiaryCnpj;
            var resultDocumentSubsidiaryInscricaoMunicipal = context.responseResult.details.subsidiaryInscricaoMunicipal;
            var resultDocumentMessageCode = context.responseResult.details.messageCode;
            var resultDocumentProtocol = context.responseResult.details.certificationProtocol;
 
            if (!requestType) {
                requestType = "RecepcionarLoteRpsRequest";
            }
 
            switch(requestType) {
                case "RecepcionarLoteRpsRequest": {
                    hasNextRequest = true;
                    requestParameters = {
                        requestType: "ConsultarLoteRpsRequest",
                        subsidiaryCnpj: resultDocumentSubsidiaryCnpj,
                        subsidiaryInscricaoMunicipal: resultDocumentSubsidiaryInscricaoMunicipal,
                        certificationProtocol: resultDocumentProtocol,
                        transaction: transaction,
                        nextOperation: getWebServiceOperationByOperationType({
                            taxAuthority: context.taxAuthority,
                            subsidiary: context.subsidiary,
                            category: getCategoryIdByExternalId("Service"),
                            operationType: getOperationTypeIdByExternalId("Check Status"),
                            environment: context.environment
                        })
                    }
 
                    break;
                }
                case "ConsultarLoteRpsRequest": {
                    hasNextRequest = checkHasNextRequest(resultDocumentMessageCode);
 
                    if (hasNextRequest) {
                        requestParameters = {
                            requestType: "ConsultarLoteRpsRequest",
                            subsidiaryCnpj: resultDocumentSubsidiaryCnpj,
                            subsidiaryInscricaoMunicipal: resultDocumentSubsidiaryInscricaoMunicipal,
                            certificationProtocol: resultDocumentProtocol,
                            transaction: transaction,
                            nextOperation: context.requestParameters.nextOperation
                        }
                    } else {
                        requestParameters = {
                            requestType: "ConsultarLoteRpsRequest",
                            transaction: transaction
                        }
                    }
                     
                    break;
                }
            }
        }
 
        return {
            hasNextRequest: hasNextRequest,
            requestParameters: requestParameters
        };
    };
 
    /**
     * Save issued or cancel invoice XML file into the file cabinet and get some informations that will be save on message item return
     * @param {Object} context
     * @param {string|number} context.subsidiary
     * @param {string|number} context.taxAuthority
     * @param {string|number} context.environment
     * @param {string|number} context.operationId
     * @param {string|number} context.operationType
     * @param {string|number} context.operationTypeExternalId
     * @param {string|number} context.recordId
     * @param {string|number} context.recordType
     * @param {string|number} context.eDocContent
     * @param {any} context.requestParameters
     * @param {any} context.responseResult
     * @param {boolean} context.responseResult.success
     * @param {string} context.responseResult.message
     * @param {string} context.responseResult.eDocContent
     * @param {any} context.responseResult.details
     *
     * @returns {Object} result
     * @returns {boolean} result.success
     * @returns {string} result.message
     * @returns {Object} result.returnDetails
     * @returns {string} result.returnDetails.documentSeries
     * @returns {string} result.returnDetails.transitoryDocumentNumber
     * @returns {string} result.returnDetails.certificationProtocol
     * @returns {string} result.returnDetails.certifiedDocumentNumber
     * @returns {string} result.returnDetails.documentAccessKey
     * @returns {Date} result.returnDetails.certificationDate
     * @returns {number} result.returnDetails.certificationReturnFileId
     * @returns {any} result.returnDetails.additionalInformation
    */
    function postProcess(context) {
        var success = false;
        var message = "";
        var fileId = null;
        var documentSeries = "";
        var transitoryDocumentNumber = 0;
        var certificationDate = null;
        var certifiedDocumentNumber = "";
        var documentAccessKey = "";
         
        var folderId = taxAuthLib.getEDocumentFolderId(context.taxAuthority, context.subsidiary, context.category);   
        var operationTypeExternalId = context.operationTypeExternalId;
 
        switch(operationTypeExternalId) {
            case 'Send E-Document': {
                var saveResult = taxAuthLib.saveFile({
                    content: context.responseResult.contents,
                    fileName: "Certified E-Document " + " - " + context.requestParameters.transaction,
                    folderId: folderId
                });
 
                success = saveResult.success;
                message = saveResult.message;
                fileId = saveResult.fileId;
 
                documentSeries = context.responseResult.details.documentSeries;
                transitoryDocumentNumber = context.responseResult.details.transitoryDocumentNumber;
                certifiedDocumentNumber = context.responseResult.details.certifiedDocumentNumber;
                certificationDate = context.responseResult.details.certificationDate;
                documentAccessKey = context.responseResult.details.documentAccessKey;
 
                break;
            }
            case 'Cancel': {
                var saveResult = taxAuthLib.saveFile({
                    content: context.responseResult.contents,
                    fileName: "Cancelled E-Document " + " - " + context.requestParameters.transaction,
                    folderId: folderId
                });
 
                success = saveResult.success;
                message = saveResult.message;
                fileId = saveResult.fileId;
 
                certificationDate = context.responseResult.details.certificationDate;
                certifiedDocumentNumber = context.responseResult.details.certifiedDocumentNumber;
                 
                break;
            }
        }
 
        return {
            success: success,
            message: message,
            returnDetails: {
                documentSeries: documentSeries,
                transitoryDocumentNumber: transitoryDocumentNumber,
                certifiedDocumentNumber: certifiedDocumentNumber,
                certificationDate: certificationDate,
                certificationProtocol: "",
                documentAccessKey: documentAccessKey,
                certificationReturnFileId: fileId, 
                additionalInformation: context.responseResult.details ? context.responseResult.details.additionalInformation : ""        
            }
        };
    };
 
    /**
     * Get document number and document series from the XML content that will be send to the Tax Government Web Service
     * @param {string} xmlContent
     *
     * @returns {Object} result
     * @returns {string} result.number
     * @returns {string} result.series
    */
    function getDocumentNumberAndSeries(xmlContent) {
        var number = "";
        var series = "";
     
        var xmlDoc = parseXml(xmlContent);
        var numberTag = taxAuthLib.getElementByXpath(xmlDoc, "//*[name()='IdentificacaoRps']//*[name()='Numero']");
        var seriesTag = taxAuthLib.getElementByXpath(xmlDoc, "//*[name()='IdentificacaoRps']//*[name()='Serie']");
     
        if (numberTag && seriesTag) {
            number = numberTag;
            series = seriesTag;
        }
     
        return {
            number: number,
            series: series
        };
    };
 
    /**
     * Modify the XML file by inserting new tags before final signature
     * @param {object} context
     * @param {string} context.unsignedXml
     * @param {stristring|numberng} context.subsidiary
     * @param {string} context.certificateId
     * @param {string|number} context.taxAuthority
     * @param {string|number} context.subsidiary
     * @param {string|number} context.operationType
     *
     * @returns {string} modifiedUnsignedXml
    */
    function modifyDocumentXmlBeforeSign(context) {
        return context.unsignedXml;
    };

   /**
    * Transforms the raw document data into the format accepted by the tax authority
    * 
    * @param context
    * @param {object} context
    * @param {number} context.subsidiaryId
    * @param {string|number} context.categoryId
    * @param {string} context.categoryExternalId
    * @param {string} context.operationTypeId
    * @param {string} context.operationTypeExternalId
    * @param {object} context.eDocumentModel
    * @returns formatted document model
    */
    function getEDocumentModel(context) {
        return context.eDocumentModel;
    };

    /**
    * Entrypoint that allows request properties customization, such as URL
    * 
    * @param {Object} context
    * @param {number} context.certRequestId
    * @param {string|number} context.subsidiary
    * @param {string|number} context.taxAuthority
    * @param {CertificationEnvironmentEnum} context.environment
    * @param {string|number} context.category
    * @param {string|number} context.operation
    * @param {string} context.eDocContent
    * @param {any} context.requestParameters
    * @param {string} context.series
    * @param {string} context.number
    * @param {string} context.url
    *
    * @returns {Object} result
    * @returns {string} result.url
    */
    function modifyReqPropertiesBeforeSend(context) {
        var customizedUrl = context.url;
        var originalParsedXml = parseXml(context.eDocContent);
        var accessKey = taxAuthLib.getFirstElementByTagName(originalParsedXml, 'ChaveAcesso');
        customizedUrl.replace('{ChaveAcesso}', accessKey);
        return {url: customizedUrl};
    };

   /**
    * Returns the tags that must be removed from log, due to the presence of sensitive customer data.
    * Example, returning ["soap:Header"] will remove from any log in message queue the entire <soap:Header>*</soap:Header> tag, even if it has attributes
    * 
    * @returns tags to be removed
    */
    function getTagsToRemoveFromLog() {
        return [""];
    };

   /**
    * Indicate if the system should try to send the request again based on the response from the web service of the tax authority. For example, in case the web service is temporarily unavailable.
    * 
    * @param {Object} context
    * @param {Object} context.response (http response)
    *
    * @returns {boolean} result    
    */
    function shouldRetryRequest(context) {
        return context.response == null || context.response.code != "200";
    }

    function getRenderedCheckStatusTemplate(context) {
        var customDataSource = {
            alias: "data",
            data: {
                subsidiaryCnpj: context.subsidiaryCnpj,
                subsidiaryInscricaoMunicipal: context.subsidiaryInscricaoMunicipal,
                protocol: context.protocol
            }
        };
        return taxAuthLib.getOperationRenderedXml({
            taxAuthority: context.taxAuthority,
            operationId: context.operationId,
            subsidiary: context.subsidiary,
            customDataSource: [customDataSource],
            signXml: true
        });
    }
 
    function errorCodeToReprocess(errorCode) {
        var errorCodeToReprocess = ['E92', 'E4', 'A02'];
     
        return errorCodeToReprocess.indexOf(errorCode) > -1;
    }
     
    function checkErrorByInitial(errorCode) {
        return errorCode.substring(0,1) === 'E';
    }
     
    function checkSuccessRecepcionarLote(errorCode, protocol) {
        if (checkErrorByInitial(errorCode)) return false;
        if (!errorCode && protocol !== "") return true;
     
        return false;
    }
     
    function checkSuccessCancelarNfse(errorCode) {
        if (checkErrorByInitial(errorCode)) return false;
        if (!errorCode) return true;
     
        return false;
    }
     
    function checkSuccessConsultarLote(errorCode, authorizationDateString) {
        if (checkErrorByInitial(errorCode)) return false;
        if (authorizationDateString !== "") return true;
     
        return false;
    }
     
    function checkHasNextRequest(errorCode) {
        if (errorCodeToReprocess(errorCode)) return true;
     
        return false;
    }
     
    function parseXml(xmlContent) {
        if (!xmlContent) return null;
        //To remove <?xml version="1.0" encoding="utf-8"?>
        xmlContent = xmlContent.replace(/\<\?xml.+?\>/g, '');
        while (xmlContent.indexOf("&") > -1) {
            xmlContent = xmlContent.replace("&", "");
        }
     
        const newXml = xml.Parser.fromString({
            text: xmlContent
        });
     
        return newXml;
    }
 
    function getWebServiceOperationByOperationType(context) {
        var searchObj = search.create({
            type: "customrecord_ecs_cert_serv_url",
            filters: [
                new search.createFilter({ name: "custrecord_ecs_crturl_l_tauth_cert", operator: "IS", values: context.taxAuthority }),
                new search.createFilter({ name: "custrecord_ecs_crturl_l_cert_env", operator: "IS", values: context.environment }),
                new search.createFilter({
                    name: "custrecord_ecs_crtsrvop_l_oper_type",
                    join: "custrecord_ecs_crturl_l_cert_operation",
                    operator: "IS",
                    values: context.operationType
                })
            ],
            columns: [
                search.createColumn({
                    name: "internalid",
                    join: "custrecord_ecs_crturl_l_cert_operation"
                })
            ]
        });
 
        var searchRes = searchObj.run().getRange({ start: 0, end: 1 });
 
        return searchRes[0].getValue({
            name: "internalid",
            join: "custrecord_ecs_crturl_l_cert_operation"
        });
    }
 
    function getCategoryIdByExternalId(externalId) {
        var searchObj = search.create({
            type: "customrecord_ecs_edoc_category",
            filters: [["externalid", "is", externalId]],
            columns: []
        });
         
        var searchRes = searchObj.run().getRange({ start: 0, end: 1 });
        return searchRes[0].id;
    }
 
    function getOperationTypeIdByExternalId(externalId) {
        var searchObj = search.create({
            type: "customrecord_ecs_cert_serv_oper_type",
            filters: [["externalid", "is", externalId]],
            columns: []
        });
         
        var searchRes = searchObj.run().getRange({ start: 0, end: 1 });
        return searchRes[0].id;
    }
 
    return {
        buildMessage: buildMessage,
        readResponse: readResponse,
        getNextRequest: getNextRequest,
        postProcess: postProcess,
        getDocumentNumberAndSeries: getDocumentNumberAndSeries,
        modifyDocumentXmlBeforeSign: modifyDocumentXmlBeforeSign,
        getEDocumentModel: getEDocumentModel,
        modifyReqPropertiesBeforeSend: modifyReqPropertiesBeforeSend,
        shouldRetryRequest: shouldRetryRequest
    }
}) 

        
Note:

This code is a sample of a working plug-in for the Brazilian tax authorities that use the GINFES standard.

The following table describes the interface functions:

Interface Function

Description

buildMessage(context)

Creates the request envelope in XML.

You should create the envelope based on the request headers and template entered in the certification service operation record. Inside the request template, you should place the e-document generated with the e-document template you created. This comprises the request envelope.

shouldRetryRequest(context)

Indicates whether the system should try to send the request again when:

  • The response from the tax authority's web service was null.

  • The response from the tax authority's web service was different than 200.

These responses indicate, for example, that the web service is down or having problems.

You should only implement this function if you need to adapt the system's behavior according with the tax authority's response.

readResponse(context)

Reads the response provided by the tax authority’s web service and extracts the information required for processing the response in NetSuite.

Each tax authority defines its own XML schema and tag standards for its e-document templates and responses. You should adapt the code to the tax authority’s particularities to enable NetSuite to read the information from the response.

getNextRequest(context)

Defines whether to send another e-document certification request for the same e-document or not.

You need to send more than one request for the same e-document if the tax authority’s web service is asynchronous. The first request sends the e-document for certification, and the following requests inquire about the status of the certification.

postProcess(context)

Finishes processing the e-document certification request. When the processing is complete, the function extracts information from the certified e-document and saves it to the e-document certification return record.

The function can be used to save the certified e-document file to the selected File Cabinet folder.

getDocumentNumberAndSeries(context)

Obtains the e-document transitory number and its series from the e-document file sent to the tax authority.

This function is only used if you have selected the E-Document for Services Sending Plug-in as the implementation for sending e-documents in the applicable method record.

modifyDocumentXmlBeforeSign(context)

Modifies the e-document file by inserting new tags or updating existing tags before the final digital signature.

This function should only be used if the tax authority requires the digital signature. If not, the function can be empty.

modifyReqPropertiesBeforeSend(context)

Modifies properties of the request before sending it to tax authority. Currently, you can only change the URL property.

This function should only be used if you need to modify the certification environment URL before sending the certification request to the tax authority. If not, you do not need to implement this function.

getTagsToRemoveFromLog()

Returns the tags that must be removed from log, due to the presence of sensitive customer data.

If no sensitive data must be removed, this function may be not implemented or an empty array may be returned.

generateTaxAuthPDF(context)

Generates PDF files from certified e-document XML files according with the operation.

This function should only be used for tax authorities that support the generation of PDF files for certification service operations.

modifyDocumentXmlAfterSign(context)

Modifies the e-document file by inserting or updating tags after the digital signature. Because tax authorities may require you to sign multiple sections of the e-document, the system runs this function after each signing.

This function is optional and should only be used if the tax authority requires a different digital signature structure, for example, with additional attributes.

checkWsAvailability(context)

Checks the availability of the tax authority's certification web service.

This optional function works with the Certification Web Services Availability portlet. It enables you to customize the validation that the portlet runs to check the availability of the web services.

By default, the Certification Web Services Availability portlet makes a standard HTTP GET request to the web service URL associated with the send e-document operation. The portlet considers that a web service is available when the HTTP status code returned is 200, 299, or 405.

For more information about the interface functions, see Tax Authority Plug-in Interface Definition.

Adding a Custom Plug-in Implementation for a Tax Authority

After you create the JavaScript file, you must create a plug-in implementation record in NetSuite.

To add a custom plug-in implementation for a tax authority:

  1. Go to Customization > Plug-ins > Plug-in Implementations > New.

  2. In the Script File field, select the script file that contains your plug-in implementation.

  3. Click Create Plug-in Implementation.

    The Select 2.0 Plug-in Type page opens.

  4. Click ECS Tax Authority Plug-in Type.

  5. In the Name field, enter a name for your implementation.

  6. In the ID field, enter an internal ID for the implementation.

    If you do not provide an ID, NetSuite provides one for you when you save the plug-in implementation record.

  7. In the Status field, select the status of the implementation.

  8. In the Log Level field, select the appropriate logging level you want for the script.

  9. (Optional) If you want, in the Description field, enter a brief description of the implementation.

  10. On the Unhandled Errors subtab, define which individuals are notified if script errors occur.

  11. Click Save.

For more information about creating the plug-in implementation, see Adding an Alternate Implementation to NetSuite.

Related Topics

General Notices