lib/shipping.ex

defmodule CorreiosEx.Shipping do
  @endpoint "http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx"
  @headers [{"Content-Type", "application/soap+xml"}]

  require Logger

  import SweetXml

  @type requestParams :: %{
          code: :string,
          origin: :string,
          destination: :string,
          weight: :positive_integer,
          length: :positive_integer,
          height: :positive_integer,
          width: :positive_integer,
          on_hand: :boolean,
          declared_value: :positive_integer,
          ack: :boolean
        }

  @type rate :: %{
          error_code: :integer,
          error_message: :string,
          code: :integer,
          eta: :string,
          price: :positive_integer
        }

  @spec rate(requestParams) :: rate
  def rate(params) do
    body = build_body(params)

    with {:ok, %HTTPoison.Response{body: body}} <- HTTPoison.post(@endpoint, body, @headers) do
      response =
        xpath(
          body,
          ~x"//cServico",
          error_code: ~x"./Erro/text()"i,
          error_message: ~x"./MsgErro/text()"s,
          code: ~x"./Codigo/text()"i,
          price: ~x"./Valor/text()"s |> transform_by(&transform_value/1),
          eta: ~x"./PrazoEntrega/text()"i |> transform_by(&calculate_delivery_date/1)
        )

      case response.error_code do
        0 -> Map.take(response, [:code, :eta, :price])
        _ -> Map.take(response, [:error_code, :error_message])
      end
    end
  end

  defp transform_value(value) do
    value
    |> String.replace(",", "")
    |> String.to_integer()
  end

  defp translate_bool(true), do: "S"
  defp translate_bool(_), do: "N"

  defp calculate_delivery_date(qty), do: Date.add(Date.utc_today(), qty)

  defp build_body(params) do
    """
    <S:Envelope xmlns:S="http://www.w3.org/2003/05/soap-envelope">
      <S:Body>
        <CalcPrecoPrazo
          xmlns="http://tempuri.org/">
          <nCdEmpresa></nCdEmpresa>
          <sDsSenha></sDsSenha>
          <nCdServico>#{params.code}</nCdServico>
          <sCepOrigem>#{params.origin}</sCepOrigem>
          <sCepDestino>#{params.destination}</sCepDestino>
          <nVlPeso>#{params.weight}</nVlPeso>
          <nCdFormato>0</nCdFormato>
          <nVlComprimento>#{params.length}</nVlComprimento>
          <nVlAltura>#{params.height}</nVlAltura>
          <nVlLargura>#{params.width}</nVlLargura>
          <nVlDiametro>0</nVlDiametro>
          <sCdMaoPropria>#{translate_bool(params.on_hand)}</sCdMaoPropria>
          <nVlValorDeclarado>#{declared_value(params)}</nVlValorDeclarado>
          <sCdAvisoRecebimento>#{translate_bool(params.ack)}</sCdAvisoRecebimento>
        </CalcPrecoPrazo>
      </S:Body>
    </S:Envelope>
    """
  end

  defp declared_value(%{declare_value: true, total_items_price: total_items_price}),
    do: total_items_price

  defp declared_value(_), do: 0
end