module CorreiosApi class Client < Ac::Base BASE_URL = "https://api.correios.com.br" MAX_RETRIES = 2 SERVICES = [ { code: "03220", name: "SEDEX CONTRATO AG" }, { code: "03298", name: "PAC CONTRATO AG" }, { code: "04227", name: "CORREIOS MINI ENVIOS CTR AG" } ] SERVICE_NAMES = { "03220" => "SEDEX CONTRATO AG", "03298" => "PAC CONTRATO AG", "04227" => "CORREIOS MINI ENVIOS CTR AG" } attr_reader :username, :password, :post_card, :contract_number, :access_token, :expires_in def initialize username:, password:, post_card:, contract_number: nil, access_token: nil, expires_in: nil @username = username @password = password @post_card = post_card @contract_number = contract_number if @access_token.nil? || @expires_in < Time.now authenticate end end def authenticate body = { numero: @post_card } response = post( "/token/v1/autentica/cartaopostagem", headers: { "Authorization" => "Basic #{Base64.strict_encode64("#{@username}:#{@password}")}", "Content-Type" => "application/json" }, body: JSON.dump(body) ) { |response| validate_response(response, "token") } @access_token = response.json.dig("token") @expires_in = Time.new(response.json.dig("expiraEm")) end def create_pre_shipping nome_remet:, cpf_cnpj_remet:, cep_remet:, rua_remet:, numero_remet:, bairro_remet:, cidade_remet:, uf_remet:, nome_dest:, cpf_cnpj_dest:, cep_dest:, rua_dest:, numero_dest:, bairro_dest:, cidade_dest:, uf_dest:, codigo_servico:, preco_pre_postagem:, itens:, peso_gramas:, altura_cm: 15, largura_cm: 15, comprimento_cm: 15 body = { idCorreios: @username, remetente: { nome: nome_remet, cpfCnpj: cpf_cnpj_remet, endereco: { cep: cep_remet, logradouro: rua_remet, numero: numero_remet, bairro: bairro_remet, cidade: cidade_remet, uf: uf_remet } }, destinatario: { nome: nome_dest, cpfCnpj: cpf_cnpj_dest, endereco: { cep: cep_dest, logradouro: rua_dest, numero: numero_dest, bairro: bairro_dest, cidade: cidade_dest, uf: uf_dest } }, codigoServico: codigo_servico, precoPrePostagem: preco_pre_postagem, numeroCartaoPostagem: @post_card, itensDeclaracaoConteudo: itens, # conteudo, quantidade, valor pesoInformado: peso_gramas, codigoFormatoObjetoInformado: "2", alturaInformada: altura_cm, larguraInformada: largura_cm, comprimentoInformado: comprimento_cm, cienteObjetoNaoProibido: "1", modalidadePagamento: "2" # 1-à vista, 2-à faturar, 3- à avista e à afaturar e 4- Prestação de contas recebimento ou pagamento # solicitarColeta: "N", # dataPrevistaPostagem: "20/09/2024" } response = post( "/prepostagem/v1/prepostagens", headers: headers, body: JSON.dump(body) ) { |response| validate_response(response, "id") } PreShipping.new(response.json) end def create_label pre_shipping body = { codigosObjeto: [ pre_shipping.codigo_objeto ], idCorreios: @username, numeroCartaoPostagem: @post_card, tipoRotulo: "P", # P (padrão) ou R (reduzido) formatoRotulo: "ET", # ET (Etiqueta) ou EV (Envelope) imprimeRemetente: "S", # S (Sim) ou N (não) layoutImpressao: "PADRAO" # PADRAO, LINEAR_100_150, LINEAR_100_80, LINEAR_A4, LINEAR_A } response = post( "/prepostagem/v1/prepostagens/rotulo/assincrono/pdf", headers: headers, body: JSON.dump(body) ) { |response| validate_response(response, "idRecibo") } sleep(0.5) # Tem que esperar o PDF ficar pronto get_label(response.json.dig("idRecibo")) end def get_label id response = get( "/prepostagem/v1/prepostagens/rotulo/download/assincrono/#{id}", headers: headers ) { |response| validate_response(response, "nome") } Label.new(response.json) end def create_rates cep_origem:, cep_destino:, weight_g: 1000, height_cm: 15, width_cm: 15, length_cm: 15 services_with_time = delivery_time(cep_origem, cep_destino) services_with_cost = delivery_cost(cep_origem, cep_destino, weight_g, height_cm, width_cm, length_cm) services_with_time.map do |service_time| service_cost = services_with_cost.find { |service| service["coProduto"] == service_time["coProduto"] } Rate.new( service_name: SERVICE_NAMES[service_time["coProduto"]], service_code: service_time["coProduto"], price: service_cost["pcFinal"], delivery_time_in_days: service_time["prazoEntrega"], max_delivery_time: service_time["dataMaxima"], message: service_time["msgPrazo"], errors: [ service_cost.dig("txErro"), service_time.dig("txErro") ].compact ) end end def delivery_time cep_origem, cep_destino body = { idLote: "1", parametrosPrazo: SERVICES.map { |service| { coProduto: service[:code], nuRequisicao: 1, cepOrigem: cep_origem, cepDestino: cep_destino } } } response = post( "/prazo/v1/nacional", headers: headers, body: JSON.dump(body) ) { |response| validate_response(response, "coProduto") } response.json end def delivery_cost cep_origem, cep_destino, weight_g, height_cm, width_cm, length_cm params_produto = SERVICES.map do |service| { coProduto: service[:code], nuRequisicao: "1", cepOrigem: cep_origem, cepDestino: cep_destino, psObjeto: weight_g, altura: height_cm, largura: width_cm, comprimento: length_cm, tpObjeto: "2" } end body = { idLote: "1", parametrosProduto: params_produto } response = post( "/preco/v1/nacional", headers: headers, body: JSON.dump(body) ) { |response| validate_response(response, "coProduto") } response.json end private def headers { "Authorization" => "Bearer #{@access_token}", "Content-Type" => "application/json" } end def validate_response(response, required_response_key) puts response.json unless response.success? ![500, 429].include?(response.code) || response.json.dig(*required_response_key) end end end