module CorreiosApi class Client < Ac::Base BASE_URL = "https://api.correios.com.br" MAX_RETRIES = 2 SAVE_RESPONSES = true SERVICES = [ { code: "03220", name: "SEDEX CONTRATO AG" }, { code: "04553", name: "SEDEX CONTRATO AGENCIA TA" }, { code: "03298", name: "PAC CONTRATO AG" }, { code: "04596", name: "PAC CONTRATO AGENCIA TA" }, { code: "04227", name: "CORREIOS MINI ENVIOS CTR AG" } ] SERVICE_NAMES = { "03220" => "SEDEX CONTRATO AG", "04553" => "SEDEX CONTRATO AGENCIA TA", "03298" => "PAC CONTRATO AG", "04596" => "PAC CONTRATO AGENCIA TA", "04227" => "CORREIOS MINI ENVIOS CTR AG" } attr_reader :username, :password, :post_card, :contract_number, :token, :expires_in def initialize username:, password:, post_card:, contract_number: nil, token: nil, expires_in: nil @username = username @password = password @post_card = post_card @contract_number = contract_number @token = token # if @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") } @token = response.json.dig("token") @expires_in = Time.new(response.json.dig("expiraEm")) end def tracking_package tracking_code, result_type = "T" response = get("/srorastro/v1/objetos/#{tracking_code}?resultado=#{result_type}") Tracking.new(response.json) end def create_pre_shipping shipper_name:, shipper_tax_id:, shipper_zipcode:, shipper_street:, shipper_address_number:, shipper_district:, shipper_city:, shipper_state_code:, receiver_name:, receiver_tax_id:, receiver_zipcode:, receiver_street:, receiver_address_number:, receiver_district:, receiver_city:, receiver_state_code:, service_code:, price:, items:, weight_g:, height_cm: 15, width_cm: 15, length_cm: 15 body = { idCorreios: @username, remetente: { nome: shipper_name, cpfCnpj: shipper_tax_id, endereco: { cep: shipper_zipcode, logradouro: shipper_street, numero: shipper_address_number, bairro: shipper_district, cidade: shipper_city, uf: shipper_state_code } }, destinatario: { nome: receiver_name, cpfCnpj: receiver_tax_id, endereco: { cep: receiver_zipcode, logradouro: receiver_street, numero: receiver_address_number, bairro: receiver_district, cidade: receiver_city, uf: receiver_state_code } }, codigoServico: service_code, precoPrePostagem: price, numeroCartaoPostagem: @post_card, itensDeclaracaoConteudo: items, # conteudo, quantidade, valor pesoInformado: weight_g, codigoFormatoObjetoInformado: "2", alturaInformada: height_cm, larguraInformada: width_cm, comprimentoInformado: length_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: "29/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.tracking_code ], 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 zipcode_from:, zipcode_to:, weight_g: 1000, height_cm: 15, width_cm: 15, length_cm: 15 services_with_time = delivery_time(zipcode_from, zipcode_to) services_with_cost = delivery_cost(zipcode_from, zipcode_to, weight_g, height_cm, width_cm, length_cm) rates = 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 ShippingQuotes.new(rates) end def delivery_time zipcode_from, zipcode_to zipcode_from_digits = CodigoPostal.new(zipcode_from).cep_digits zipcode_to_digits = CodigoPostal.new(zipcode_to).cep_digits body = { idLote: "1", parametrosPrazo: SERVICES.map { |service| {coProduto: service[:code], nuRequisicao: 1, cepOrigem: zipcode_from_digits, cepDestino: zipcode_to_digits} } } response = post( "/prazo/v1/nacional", headers: headers, body: JSON.dump(body) ) { |response| validate_response(response, "coProduto") } response.json end def delivery_cost zipcode_from, zipcode_to, weight_g, height_cm, width_cm, length_cm zipcode_from_digits = CodigoPostal.new(zipcode_from).cep_digits zipcode_to_digits = CodigoPostal.new(zipcode_to).cep_digits params_produto = SERVICES.map do |service| { coProduto: service[:code], nuRequisicao: "1", cepOrigem: zipcode_from_digits, cepDestino: zipcode_to_digits, 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 #{@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