UNPKG

correios

Version:

Correios Node.js library

336 lines (263 loc) 11.8 kB
var request = require('request'), cheerio = require('cheerio'), querystring = require('querystring'), xml2js = require('xml2js'); // really don't wanna do this for all strings... String.prototype.clean = function() { return this.replace(/\s+/g, ' ').trim(); } // search for object key, passing it's value keyOf = function( object, value ){ for (var key in object){ if (hasOwnProperty.call(object, key) && object[key] === value) return key; } return null; } var CorreiosOptions = { baseUrls: { track: 'http://websro.correios.com.br/sro_bin/txect01$.QueryList?P_LINGUA=001&P_TIPO=001&P_COD_UNI=', price: 'http://ws.correios.com.br/calculador/CalcPrecoPrazo.aspx' }, // Correios delivery types translation map // side comments are from correios table (including portuguese mistakes) deliveryTypes: { sedex: 40010, // SEDEX sem contrato sedex_contract_1: 40096, // SEDEX com contrato sedex_contract_2: 40436, // SEDEX com contrato sedex_contract_3: 40444, // SEDEX com contrato sedex_contract_4: 40568, // SEDEX com contrato sedex_contract_5: 40606, // SEDEX com contrato sedex_cobrar: 40045, // SEDEX a Cobrar, sem contrato sedex_a_cobrar_contract: 40126, // SEDEX a Cobrar, com contrato sedex_10: 40215, // SEDEX 10, sem contrato sedex_hoje: 40290, // SEDEX Hoje, sem contrato esedex_contract: 81019, // e-SEDEX, com contrato esedex_contract_priority: 81027, // e-SEDEX Prioritário, com conrato esedex_contract_express: 81035, // e-SEDEX Express, com contrato esedex_contract_1: 81868, // (Grupo 1) e-SEDEX com contrato esedex_contract_2: 81833, // (Grupo 2) e-SEDEX com contrato esedex_contract_3: 81850, // (Grupo 3) e-SEDEX com contrato pac: 41106, // PAC sem contrato pac_contract: 41068 // PAC com contrato }, // Correios error types translation map errorTypes: { '0': 'Processamento com sucesso', '-1': 'Código de serviço inválido', '-2': 'CEP de origem inválido', '-3': 'CEP de destino inválido', '-4': 'Peso excedido', '-5': 'O Valor Declarado não deve exceder R$ 10.000,00', '-6': 'Serviço indisponível para o trecho informado', '-7': 'O Valor Declarado é obrigatório para este serviço', '-8': 'Este serviço não aceita Mão Própria', '-9': 'Este serviço não aceita Aviso de Recebimento', '-10': 'Precificação indisponível para o trecho informado', '-11': 'Para definição do preço deverão ser informados, também, o comprimento, a largura e altura do objeto em centímetros (cm).', '-12': 'Comprimento inválido.', '-13': 'Largura inválida.', '-14': 'Altura inválida.', '-15': 'O comprimento não pode ser maior que 105 cm.', '-16': 'A largura não pode ser maior que 105 cm.', '-17': 'A altura não pode ser maior que 105 cm.', '-18': 'A altura não pode ser inferior a 2 cm.', '-20': 'A largura não pode ser inferior a 11 cm.', '-22': 'O comprimento não pode ser inferior a 16 cm.', '-23': 'A soma resultante do comprimento + largura + altura não deve superar a 200 cm.', '-24': 'Comprimento inválido.', '-25': 'Diâmetro inválido', '-26': 'Informe o comprimento.', '-27': 'Informe o diâmetro.', '-28': 'O comprimento não pode ser maior que 105 cm.', '-29': 'O diâmetro não pode ser maior que 91 cm.', '-30': 'O comprimento não pode ser inferior a 18 cm.', '-31': 'O diâmetro não pode ser inferior a 5 cm.', '-32': 'A soma resultante do comprimento + o dobro do diâmetro não deve superar a 200 cm.', '-33': 'Sistema temporariamente fora do ar. Favor tentar mais tarde.', '-34': 'Código Administrativo ou Senha inválidos.', '-35': 'Senha incorreta.', '-36': 'Cliente não possui contrato vigente com os Correios.', '-37': 'Cliente não possui serviço ativo em seu contrato.', '-38': 'Serviço indisponível para este código administrativo' } }; /** * Check if supplied cep is valid * @param {string} $cep CEP code * @return {Boolean} */ function isZipcodeValid ($cep) { return $cep.match(/^\d{5}\-?\d{3}$/); } var correios = module.exports = { /** * Track some package in Correios website * * @param {string} code correios tracking code * @param {Function} callback callback to execute when have some data or error */ track: function ( code, callback ) { if ( !callback ) throw new Error('Correios error -> you must specify a callback'); code = code.toUpperCase(); var url = CorreiosOptions.baseUrls.track + code; return request( url, {}, function( error, response, body ) { if ( error ) return callback( error ); if ( response.statusCode != 200 ) return callback( new Error('Correios error -> http status code:' + response.statusCode) ); // parse html $ = cheerio.load( body, { lowerCaseTags: true }); // has tracking table? if ( !$('table').length ) { var text = $('p').text().clean(); if ( text.indexOf(code) > -1 ) return callback( null, text ); // valid tracking code at least else return callback( new Error('Correios error -> invalid tracking code') ); // invalid tracking code dude. } var trackData = [], row1; // * will ignore first TR, correios doesn't know thead... // * every record uses two tr's // * will use the same keys of correios mobile but using english (doesnt make sense the keys in ptbr) // (almost decent html, but has a simple captacha and I don't wanna to eval it) for ( var i = 1; i < $('tr').length; i += 2 ) { row1 = $('tr').eq(i); trackData.push({ date : row1.children('td').eq(0).text().clean(), spot : row1.children('td').eq(1).text().clean().toLowerCase(), // remembers me sheldon from bbt status : row1.children('td').eq(2).text().clean().toLowerCase(), guidance : $('tr').eq(i+1).children('td').eq(0).text().clean().toLowerCase() }); } // return the track data with last updates first // if you need the lasts first, make an array reverse! return callback( null, trackData ); }); }, /** * Return price and estimated delivery * * @param {object} options * This will received the following vars: * <p> * {string} serviceType 'sedex_contract_1', 'sedex_contract_2', 'sedex_contract_3', * 'sedex_contract_4', 'sedex_contract_5', 'sedex_cobrar', * 'sedex_a_cobrar_contract', 'sedex_10', 'sedex_hoje', * 'esedex_contract', 'esedex_contract_priority', * 'esedex_contract_express', 'esedex_contract_1', * 'esedex_contract_2', 'esedex_contract_3', 'pac', * 'pac_contract' * <p> * {string} from CEP like 00000-000 or 00000000 [required] * <p> * {string} to CEP like 00000-000 or 00000000 [required] * <p> * {Boolean} handDelivery should use hand delivery service? [empty means no] * <p> * {float} value value of the package in case you need insurance [empty means 0.00] * <p> * {Boolean} notice wants receive a notice when package is delivered? [empty means no] * <p> * {int} format 1 - Box/Package, 2 - Coil/Prism, 3 - Envelope [empty means box] * <p> * {int} weight Package weight in KG (including package) [required] * <p> * {int} width Package width in CM (including package) [required by package type] * <p> * {int} height Package height in CM (including package) [required by package type] * <p> * {int} length Package length in CM (including package) [required by package type] * <p> * {int} diameter Package diameter in CM (including package) [required by package type] * <p> * {string} companyCode Administrative Code with ECT [can be empty] * <p> * {string} companyPwd First 8 digits of CNPJ used in ECT contract. [can be empty] * * @param {Function} callback * @return {error|data} */ getPrice: function( options, callback ) { if ( !callback ) throw new Error('Correios error -> you must specify a callback'); // internal assigns // delivery info var serviceType = CorreiosOptions.deliveryTypes[ options.serviceType ] || '', from = options.from || '', to = options.to || '', handDelivery = !!options.handDelivery ? 'S' : 'N', // correios uses this enum value = !!options.value ? options.value : 0.0, notice = !!options.notice ? 'S' : 'N', // correios uses this enum // package info format = options.format || 1, width = options.width || 0, height = options.height || 0, length = options.length || 0, diameter = options.diameter || 0, weight = options.weight || 0, // contract info companyCode = options.companyCode || "", companyPwd = options.companyPwd || ""; var qs = querystring.stringify({ StrRetorno: 'xml', nCdServico: serviceType, scepOrigem: from, scepDestino: to, nVlPeso: weight, sCdMaoPropria: handDelivery, nVlValorDeclarado: value, sCdAvisoRecebimento: notice, nCdEmpresa: companyCode, sDsSenha: companyPwd, nCdFormato: format, nVlComprimento: length, nVlAltura: height, nVlLargura: width, nVlDiametro: diameter }); makeRequest({ method: 'post', qs: qs }, function ( error, data ) { if ( error ) return callback( new Error('Correios error -> ' + error) ); if (!data.cServico) return callback( new Error('Correios error -> invalid response contents' ) ); var data = data.cServico; if ( data.Erro != 0 ) return callback( new Error('Correios error -> get this error on response: ' + CorreiosOptions.errorTypes[data.Erro] || 'unknown error code' ) ); return callback( null, { serviceType: keyOf(CorreiosOptions.deliveryTypes, parseInt(data.Codigo)), estimatedDelivery: parseInt(data.PrazoEntrega), GrandTotal: parseFloat(data.Valor.replace(/,/g, '.')), handDeliveryPrice: parseFloat(data.ValorMaoPropria.replace(/,/g, '.')), deliveryNoticePrice: parseFloat(data.ValorAvisoRecebimento.replace(/,/g, '.')), declaredValuePrice: parseFloat(data.ValorValorDeclarado.replace(/,/g, '.')), homeDelivery: data.EntregaDomiliar == 'S' ? true : false, saturdayDelivery: data.EntregaSabado == 'S'? true : false }); }); } }; function makeRequest (options, callback) { request[ options.method ]( { uri: CorreiosOptions.baseUrls.price + '?' + options.qs }, function( error, response, body ) { if ( error ) return callback(error); if ( response.statusCode != 200 ) return callback( new Error('Correios error -> http status code:' + response.statusCode) ); // convert XML to JSON var parser = new xml2js.Parser({ normalize: true, explicitRoot: false, explicitArray: false }); parser.parseString( body, function( error, result ) { if ( error ) return callback( new Error('Correios error -> error parsing correios xml response') ); // finally the response we want (or not) return callback( null, result ); }); }); }