Argument error: Unknown Field in association

I’m having trouble making a associated save. I have a Payment object that sometimes have a associated ExternalAuthorization object.

  • They are related trought the external_authorization_id column,
  • The migration where created as below:
defmodule Payment.Repo.Migrations.CreateExternalAuthorization do
	use Ecto.Migration


  	def up do
  		# Credit Card
  		create table(:external_authorization) do
  			#add(:id, type: :uuid, primary_key: true)
  			#add(:payment_id, references("payment"))
  			add(:cavv, :string, size: 255)
  			add(:xid, :string, size: 255)
  			add(:eci, :string, size: 4)
  			add(:version, :string, size: 4)
  			add(:reference_id, :string, size: 255)
  			add(:acquirer_id, references("acquirer"))

  			timestamps(inserted_at: :created_at, updated_at: :updated_at, type: :timestamptz)
  		end

  		alter table(:payment) do
  			add(:external_authorization_id, references("external_authorization"))
  		end
  	end

  	def down do
  		alter table(:payment) do
  			remove(:external_authorization_id)
  		end
  	end
end



The associations are linked in the files below:

gateway_web/lib/controllers/pay_controller.ex

defmodule GatewayWeb.PayController do
  @moduledoc """
  Handle new Payments
  """
  use GatewayWeb, :controller

  import GatewayWeb, only: [render_payment: 2]

  alias Gateway.{
    Acquirer,
    Payload,
    Payment
  }

  alias Gateway.Pay.{
    Antifraud,
    BankBillet,
    CreditCard,
    ExternalAuthorization,
    PayPal
  }

  alias Gateway.Actions.Analyse.Bureau
  alias GatewayWeb.FallbackController

  action_fallback(FallbackController)

  @doc """
  Convert the params in Payload struct before execute the actions
  """
  def action(conn, _) do
    with {:ok, payload} <- Payload.from_params(conn.params) do
      acquirer = Acquirer.init(payload.acquirer)
      args = [conn, acquirer, payload, conn.private[:jwt]]

      apply(__MODULE__, action_name(conn), args)
    end
  end

  @doc """
  CreditCard payment with Antifraud
  """
  def pay(conn, acquirer, %{antifraud: antifraud, credit_card: credit_card} = payload, auth)
      when not is_nil(antifraud) and not is_nil(credit_card) do
    with {:ok, payment} <- Payment.with_antifraud(payload, auth),
         {:ok, updated} <- Bureau.analyse(payment, payload, auth),
         {:ok, payment} <- Antifraud.pay(acquirer, payload, updated) do
      render_payment(conn, payment)
    end
  end

  @doc """
  Payment with BankBillet
  """
  def pay(conn, acquirer, %{bank_billet: bb} = payload, auth) when not is_nil(bb) do
    with {:ok, payment} <- Payment.with_bank_billet(payload, auth),
         {:ok, updated} <- BankBillet.pay(payment, acquirer) do
      render_payment(conn, updated)
    end
  end

  @doc """
  Payment with PayPal
  """
  def pay(conn, acquirer, %{paypal: paypal} = payload, auth) when not is_nil(paypal) do
    with {:ok, payment} <- Payment.with_paypal(payload, auth),
         {:ok, payment} <- PayPal.pay(payment, payload, acquirer, auth) do
      render_payment(conn, payment)
    end
  end

  @doc """
  Payment with CreditCard
  """
  def pay(conn, acquirer, payload, auth) do
    with {:ok, payment} <- Payment.with_credit_card(payload, auth),
         {:ok, upload} <- CreditCard.pay(payment, payload, acquirer) do
      render_payment(conn, upload)
    end
  end
end

gateway/lib/payload.ex

defmodule Gateway.Payload do
  @moduledoc """
  Schema of the incoming request body data
  """
  use Ecto.Schema

  import Ecto.Changeset
  import Gateway.Gettext

  alias Ecto.UUID

  alias Gateway.Payload.{
    Antifraud,
    BankBillet,
    CreditCard,
    ExternalAuthorization,
    Customer,
    Items,
    PayPal
  }

  @default_acquirer Application.get_env(:gateway, :acquirers)[:default]
  @bank_billet_acquirer Application.get_env(:gateway, :acquirers)[:bank_billet_default]
  @default_currency Application.get_env(:gateway, :currencies)[:default]

  @primary_key false
  embedded_schema do
    field(:external_id, :string)
    field(:amount, :integer)
    field(:interest, :integer)
    field(:acquirer_config, UUID)
    field(:currency, :string, default: @default_currency)
    field(:acquirer, :string)
    field(:capture, :boolean, default: false)
    field(:soft_descriptor, :string)
    field(:postback_url, :string)

    embeds_one(:customer, Customer)
    embeds_one(:credit_card, CreditCard)
    embeds_one(:external_authorization, ExternalAuthorization)
    embeds_one(:bank_billet, BankBillet)
    embeds_one(:paypal, PayPal)
    embeds_one(:antifraud, Antifraud)

    embeds_many(:items, Items)
  end

  @required_fields ~w(
    external_id
    amount
    interest
  )a

  @optional_fields ~w(
    soft_descriptor
    acquirer
    acquirer_config
    capture
    currency
    postback_url
  )a

  @doc """
  Format data from a map into a Payload
  """
  def from_params(data) when is_map(data) do
    acquirers = Application.get_env(:gateway, :acquirers)[:valid]
    currencies = Application.get_env(:gateway, :currencies)[:valid]

    %__MODULE__{}
    |> cast(data, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
    # Sanitize data
    |> update_change(:external_id, &String.trim/1)
    |> update_change(:acquirer, &String.trim/1)
    |> update_change(:acquirer, &String.downcase/1)
    |> update_change(:acquirer_config, &String.trim/1)
    |> update_change(:currency, &String.trim/1)
    |> update_change(:soft_descriptor, &replace_special_chars/1)
    |> update_change(:currency, &String.upcase/1)
    # Validate data
    |> validate_inclusion(:acquirer, acquirers)
    |> validate_inclusion(:currency, currencies)
    |> validate_number(:amount, greater_than: 0)
    |> validate_number(:interest, greater_than_or_equal_to: 0)
    |> validate_length(:soft_descriptor, max: 13)
    |> validate_url(:postback_url)
    # Embeded data
    |> cast_embed(:bank_billet)
    |> cast_embed(:credit_card)
    |> cast_embed(:external_authorization)
    |> cast_embed(:paypal)
    |> cast_embed(:antifraud)
    |> cast_embed(:customer, required: true)
    |> cast_embed(:items, required: true)
    |> apply_aquirer()
    |> apply_action(:insert)
  end

  defp replace_special_chars(field) do
    field
    |> String.normalize(:nfd)
    |> String.replace(~r/[^A-z\s]/u, "")
    |> String.trim()
  end

  defp apply_aquirer(changeset) do
    acquirer = get_field(changeset, :acquirer, nil)
    bank_billet = get_field(changeset, :bank_billet, nil)

    cond do
      not is_nil(acquirer) ->
        changeset

      is_nil(bank_billet) ->
        changeset
        |> put_change(:acquirer, @default_acquirer)

      true ->
        changeset
        |> put_change(:acquirer, @bank_billet_acquirer)
    end
  end

  defp validate_url(changeset, field) do
    url = get_field(changeset, field, nil)

    if is_nil(url) do
      changeset
    else
      case :http_uri.parse(url) do
        {:ok, _} ->
          changeset

        {:error, _} ->
          changeset
          |> add_error("postback_url", dgettext("errors", "invalid postback url format"))
      end
    end
  end
end

apps/payment/lib/new.ex

defmodule Payment.New do
  @moduledoc false

  alias Ecto.{
    Changeset,
    Multi
  }

  alias Payment.Repo

  alias Payment.Schema.{
    Acquirer,
    Antifraud,
    AntifraudData,
    BankBillet,
    CreditCard,
    ExternalAuthorization,
    CreditCardTokenHistory,
    Currency,
    Customer,
    Item,
    PayPal,
    QRCode,
    Status,
    StatusHistory,
    Type
  }

  def with_bank_billet(payload, config, auth) do
    bank_billet = payload.bank_billet
    customer = payload.customer

    Multi.new()
    |> Multi.run(:customer, &customer(&1, customer, auth))
    |> Multi.run(:payment, &new_payment(&1, Type.bank_billet(), payload, auth, config))
    |> Multi.run(:history, &add_history/1)
    |> Multi.run(:bank_billet, &bank_billet(&1, bank_billet))
    |> Multi.run(:associated, &payment_method(&1))
    |> insert([
      :acquirer_config,
      :bank_billet,
      :status,
      customer: :address
    ])
  end

  def with_qrcode(payload, config, auth) do
    Multi.new()
    |> Multi.run(:customer, &customer(&1, payload.customer, auth))
    |> Multi.run(:payment, &new_payment(&1, Type.qrcode(), payload, auth, config))
    |> Multi.run(:qrcode, &qrcode(&1))
    |> Multi.run(:history, &add_history/1)
    |> Multi.run(:associated, &payment_method(&1))
    |> insert([
      :acquirer_config,
      :details,
      :qrcode,
      :status,
      customer: :address
    ])
  end

  def with_credit_card(payload, config, auth) do
    credit_card = payload.credit_card
    installments = credit_card.installments
    customer = payload.customer
    external_authorization = payload.external_authorization

    Multi.new()
    |> Multi.run(:customer, &customer(&1, customer, auth))
    |> Multi.run(:payment, &new_payment(&1, Type.credit_card(), payload, auth, config))
    |> Multi.run(:history, &add_history/1)
    |> Multi.run(:credit_card, &credit_card(&1, credit_card))
    |> Multi.run(:external_authorization, &external_authorization(&1, external_authorization))
    |> Multi.run(:associated, &payment_method(&1, installments))
    |> insert([
      :acquirer_config,
      :status,
      :credit_card,
      :external_authorization,
      customer: :address,
      credit_card: :credit_card_token
    ])
    |> credit_card_history()
    |> loaded_virtual_fields(payload)
  end

  defp external_authorization(%{payment: _payment}, external_authorization) do

    data = %{
      cavv: external_authorization.cavv,
      xid: external_authorization.xid,
      eci: external_authorization.eci,
      version: external_authorization.version,
      reference_id: external_authorization.reference_id,
      acquirer_id: 1
    }

    external_authorization
    |> Map.from_struct()
    |> Map.delete(:token)
    |> Map.merge(data)
    |> ExternalAuthorization.new()
    |> Repo.insert()
  end

  def with_antifraud(payload, config, antifraud, auth) do
    credit_card = payload.credit_card
    installments = credit_card.installments
    customer = payload.customer

    Multi.new()
    |> Multi.run(:customer, &customer(&1, customer, auth))
    |> Multi.run(:payment, &new_payment(&1, Type.credit_card(), payload, auth, config, antifraud))
    |> Multi.run(:antifraud_data, &antifraud_data(&1, payload.antifraud))
    |> Multi.run(:history, &add_history/1)
    |> Multi.run(:credit_card, &credit_card(&1, credit_card))
    |> Multi.run(:associated, &payment_method(&1, installments))
    |> insert([
      :acquirer,
      :acquirer_config,
      :antifraud,
      :antifraud_config,
      :antifraud_data,
      :currency,
      :items,
      :status,
      :credit_card,
      customer: :address,
      credit_card: :credit_card_token
    ])
    |> credit_card_history()
    |> loaded_virtual_fields(payload)
  end

  def with_paypal(payload, config, auth) do
    paypal = payload.paypal
    installments = paypal.installments
    customer = payload.customer

    Multi.new()
    |> Multi.run(:customer, &customer(&1, customer, auth))
    |> Multi.run(:payment, &new_payment(&1, Type.paypal(), payload, auth, config))
    |> Multi.run(:history, &add_history/1)
    |> Multi.run(:paypal, &paypal(&1, paypal))
    |> Multi.run(:associated, &payment_method(&1, installments))
    |> insert([
      :acquirer_config,
      :status,
      :paypal,
      customer: :address
    ])
  end

  defp insert(multi, preload) do
    case multi |> Repo.transaction() do
      {:ok, transaction} ->
        {:ok, Repo.preload(transaction.associated, preload)}

      {:error, reason} ->
        {:error, reason}

      {:error, _, reason, _} ->
        {:error, reason}
    end
  end

  defp customer(_, customer, auth) do
    customer
    |> Customer.get_or_create(auth.company_id)
    |> Repo.insert_or_update()
  end

  defp new_payment(%{customer: customer}, type, payload, auth, config) do
    data = %{
      capture: payload.capture,
      type_id: type,
      status_id: Status.get_id(:started),
      company_id: auth.company_id,
      currency_id: Currency.get_id(payload.currency),
      customer_id: customer.id,
      acquirer_id: Acquirer.get_id(payload.acquirer),
      antifraud_id: Antifraud.get_id(payload.antifraud),
      application_id: Integer.to_string(auth.application_id),
      acquirer_config_id: config.id
    }

    do_new_payment(payload, data)
  end

  defp new_payment(%{customer: customer}, type, payload, auth, config, fraud_config) do
    data = %{
      capture: payload.capture,
      type_id: type,
      status_id: Status.get_id(:started),
      company_id: auth.company_id,
      currency_id: Currency.get_id(payload.currency),
      customer_id: customer.id,
      acquirer_id: Acquirer.get_id(payload.acquirer),
      antifraud_id: Antifraud.get_id(payload.antifraud),
      application_id: Integer.to_string(auth.application_id),
      acquirer_config_id: config.id,
      antifraud_config_id: fraud_config.id
    }

    do_new_payment(payload, data)
  end

  defp do_new_payment(payload, data) do
    items = Item.from_struct_list(payload.items)

    payload
    |> Map.from_struct()
    |> Map.merge(data)
    |> Payment.new()
    |> Changeset.put_assoc(:items, items)
    |> Repo.insert()
  end

  defp add_history(%{payment: payment}) do
    payment
    |> StatusHistory.new()
    |> Repo.insert()
  end

  defp credit_card_history({:error, reason}), do: {:error, reason}

  defp credit_card_history({:ok, %{credit_card: credit_card} = payment}) do
    with {:ok, _} <- CreditCardTokenHistory.new(credit_card) do
      {:ok, payment}
    end
  end

  # Credit_card payment with token
  defp credit_card(%{payment: payment, customer: customer}, %{token: token} = credit_card)
       when not is_nil(token) do
    CreditCard.get_card_token(
      payment.acquirer_id,
      customer.id,
      credit_card.token
    )
  end

  defp credit_card(%{payment: payment}, credit_card) do
    credit_card_numbers = CreditCard.numbers(credit_card.number)

    data = %{
      expiration: CreditCard.get_expiration(credit_card.expiration),
      masked: credit_card_numbers.masked,
      customer_id: payment.customer_id,
      acquirer_id: payment.acquirer_id,
      first_six: credit_card_numbers.first_six,
      last_four: credit_card_numbers.last_four
    }

    credit_card
    |> Map.from_struct()
    |> Map.delete(:token)
    |> Map.merge(data)
    |> CreditCard.get_or_create()
    |> Repo.insert_or_update()
  end

  defp paypal(%{payment: payment}, paypal) do
    data = %{
      customer_id: payment.customer_id,
      acquirer_id: payment.acquirer_id
    }

    paypal
    |> Map.from_struct()
    |> Map.merge(data)
    |> PayPal.new()
    |> Repo.insert()
  end

  defp loaded_virtual_fields({:error, reason}, _), do: {:error, reason}

  defp loaded_virtual_fields(
         {:ok, %{credit_card: %{credit_card_token: token}} = payment},
         payload
       )
       when not is_nil(token) do
    cvv =
      if is_nil(payload.credit_card.cvv) do
        token.cvv
      else
        payload.credit_card.cvv
      end

    credit_card = %{payment.credit_card | number: token.number, cvv: cvv}

    {:ok, %{payment | credit_card: credit_card}}
  end

  defp loaded_virtual_fields({:ok, payment}, payload) do
    credit_card = %{
      payment.credit_card
      | number: payload.credit_card.number,
        cvv: payload.credit_card.cvv
    }

    {:ok, %{payment | credit_card: credit_card}}
  end

  defp bank_billet(_, %{expiration: expiration} = bank_billet) do
    bank_billet
    |> Map.from_struct()
    |> Map.put(:expiration, BankBillet.get_expiration(expiration))
    |> BankBillet.new()
    |> Repo.insert()
  end

  defp qrcode(_) do
    %{}
    |> Map.put(:expiration, QRCode.get_expiration())
    |> QRCode.new()
    |> Repo.insert()
  end

  defp payment_method(%{payment: payment, credit_card: credit_card}, installments) do
    payment
    |> Payment.changeset(%{})
    |> Changeset.put_change(:credit_card_id, credit_card.id)
    |> Changeset.put_change(:installments, installments)
    |> Repo.update()
  end

  defp payment_method(%{payment: payment, paypal: paypal}, installments) do
    payment
    |> Payment.changeset(%{})
    |> Changeset.put_change(:paypal_id, paypal.id)
    |> Changeset.put_change(:installments, installments)
    |> Repo.update()
  end

  defp payment_method(%{payment: payment, bank_billet: bank_billet}) do
    payment
    |> Payment.changeset(%{})
    |> Changeset.put_change(:bank_billet_id, bank_billet.id)
    |> Repo.update()
  end

  defp payment_method(%{payment: payment, qrcode: qrcode}) do
    payment
    |> Payment.changeset(%{})
    |> Changeset.put_change(:qrcode_id, qrcode.id)
    |> Repo.update()
  end

  defp antifraud_data(%{payment: payment}, antifraud) do
    payment
    |> AntifraudData.new(antifraud)
    |> Repo.insert()
  end
end

apps/payment/lib/payment.ex

defmodule Gateway.Payment do
  @moduledoc """
  Gateway payment module handle different payment types workflows
  """

  alias Payment.Schema.{
    AcquirerConfig,
    AntifraudConfig
  }

  alias Payment.New

  def with_antifraud(payload, auth) do
    {:ok, antifraud} = AntifraudConfig.get_config(payload, auth.company_id)

    New.with_antifraud(payload, acquirer_config(payload, auth), antifraud, auth)
  end

  def with_bank_billet(payload, auth),
    do: New.with_bank_billet(payload, acquirer_config(payload, auth), auth)

  def with_credit_card(payload, auth),
    do: New.with_credit_card(payload, acquirer_config(payload, auth), auth)

  def with_qrcode(payload, auth),
    do: New.with_qrcode(payload, acquirer_config(payload, auth), auth)

  def with_paypal(payload, auth),
    do: do_create(payload, acquirer_config(payload, auth), auth)

  defp do_create(payload, %{keys: %{"manual_review" => true}} = config, auth) do
    New.with_paypal(%{payload | capture: false}, config, auth)
  end

  defp do_create(payload, config, auth), do: New.with_paypal(payload, config, auth)

  defp acquirer_config(payload, %{company_id: company_id}) do
    with {:ok, config} <- AcquirerConfig.get_config(payload, company_id) do
      config
    end
  end
end

apps/payment/lib/schema.ex

defmodule Payment.Schema do
  @moduledoc """
  Payment Schema
  """
  def schema do
    quote do
      use Ecto.Schema

      import Ecto
      import Ecto.Changeset
      import Ecto.Query

      alias Payment.Repo

      alias Payment.Schema.{
        Acquirer,
        AcquirerConfig,
        Antifraud,
        AntifraudConfig,
        AntifraudData,
        AntifraudDecision,
        BankBillet,
        CaptureHistory,
        CreditCard,
        ExternalAuthorization,
        CreditCardToken,
        CreditCardTokenHistory,
        Currency,
        Customer,
        CustomerAddress,
        DeclinedReason,
        Details,
        Item,
        PayPal,
        PostbackHistory,
        QRCode,
        RefundHistory,
        Status,
        StatusHistory,
        Type
      }
    end
  end

  @doc false
  defmacro __using__(which) when is_atom(which) do
    apply(__MODULE__, which, [])
  end
end

apps/gateway/lib/payload/external_authorization.ex

defmodule Gateway.Payload.ExternalAuthorization do
  @moduledoc """
  CreditCard data in the incoming request body
  """
  use Ecto.Schema
  #alias Ecto.UUID

  import Ecto.Changeset
  #import Gateway.Gettext

  @primary_key false
  embedded_schema do
    field(:cavv, :string)
    field(:xid, :string)
    field(:eci, :string)
    field(:version, :string)
    field(:reference_id, :string)
    
  end

  @required_fields ~w(cavv xid eci version reference_id)a
  @optional_fields ~w()a

  # @doc false
  # def changeset(struct, %{"token" => token} = data) when not is_nil(token) do
  #   struct
  #   |> cast(data, @required_token_fields ++ @optional_token_fields)
  #   |> validate_required(@required_token_fields)
  #   # Sanitize data
  #   |> update_change(:cvv, &String.trim/1)
  #   |> update_change(:token, &String.trim/1)
  #   # Validate Data
  #   |> validate_binary_token()
  #   |> validate_number(:installments, greater_than_or_equal_to: 1)
  #   |> validate_number(:installments, less_than_or_equal_to: @installments)
  # end

  def changeset(struct, data) do
    struct
    |> cast(data, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
    # Sanitize data
    |> update_change(:cavv, &String.trim/1)
    |> update_change(:xid, &String.trim/1)
    |> update_change(:eci, &String.trim/1)
    |> update_change(:version, &String.trim/1)
    |> update_change(:reference_id, &String.trim/1)
  end

  # # Remove non digits
  # defp clean_nondigit_char(field) do
  #   Regex.replace(~r/\D/, field, "")
  # end

end

apps/payment/lib/schema/external_authorization.ex

defmodule Payment.Schema.ExternalAuthorization do
  @moduledoc """
  CreditCardToken Schema
  """
  use Payment.Schema, :schema
  alias Ecto.Kms

  @primary_key {:id, :id, autogenerate: true}
  schema "external_authorization" do
    field(:cavv, Kms)
    field(:xid, Kms)
    field(:eci, Kms)
    field(:version, Kms)
    field(:reference_id, Kms)

    #belongs_to(:payment, Payment)
    belongs_to(:acquirer, Acquirer)

    timestamps(inserted_at: :created_at, type: :utc_datetime)
  end

  @required_fields ~w(cavv
                      xid
                      eci
                      version
                      reference_id)a

  @optional_fields ~w(acquirer_id)a
  

  @doc false
  def changeset(%__MODULE__{} = external_authorization, attrs) do
    external_authorization
    |> cast(attrs, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
  end

   @doc false
  def new(attrs) do
    __MODULE__.changeset(%__MODULE__{}, attrs)
  end

  def to_map(external_authorization) when is_nil(external_authorization), do: nil



  def to_map(%__MODULE__{} = external_authorization)  do
     %{
      cavv: external_authorization.cavv,
      xid: external_authorization.xid,
      eci: external_authorization.eci,
      version: external_authorization.version,
      reference_id: external_authorization.reference_id,
      acquirer: 1
    }
  end

end

The payload I’m sending via Api is below:

{
    "acquirer": "cielo",
    "externalId": "c05b23d2-fc67-11e7-96f5-d7fc82149a94",
    "company_id" : 1,
    "amount": 100,
    "interest": 0,
    "creditCard": {
        "installments": 1,
        "number": "5555555555555555",
        "holder": "Teste",
        "expiration": "02/2028",
        "cvv": "Teste",
        "save": false
    },
    "externalAuthorization": {
        "cavv": "Y2FyZGluYWxjb21tZXJjZWF1dGg=",
        "xid": "Y2FyZGluYWxjb21tZXJjZWF1dGg=",
        "eci": "05",
        "version": "2",
        "reference_id": "e473919f-a33d-4cb7-b172-1c0e826025f7"
    },
    "customer": {
        "externalId": "297229",
        "name": "Nome Teste",
        "document": "123.345.321-92",
        "email": "teste@teste.com",
        "phone": "(11) 99999-9999",
        "gender": "male",
        "address": {
            "street": "teste",
            "number": "444",
            "complement": "655",
            "zipcode": "04563-060",
            "city": "São Paulo",
            "state": "SP",
            "country": "BR",
            "district": "Brooklin"
        }
    },
    "items": [
        {
            "sku": "378283",
            "type": "ticket",
            "name": "teste",
            "unitPrice": 500,
            "quantity": 1
        }
    ]
}

Obs: ExternalAuthorization is optional

I’m receiving the error: {{%ArgumentError{message: “unknown field external_authorization_id. Only fields, embeds and associations (except :through ones) are supported in changesets”}.

Despite the large amount of code posted, the function Payment.Schema.Payment.new/1 isn’t included. I suspect that’s where the issue is, given that external_authorization_id is on the corresponding table.

BTW, be very very very careful with CVVs - even logging one is a PCI-compliance nightmare.

Tks a lot for the fast response!

Payment.Schema.Payment.new/1 int the following file:

defmodule Payment do
  @moduledoc """
  Schema of the Payment
  """
  use Payment.Schema, :schema

  @primary_key {:id, :binary_id, autogenerate: true}
  schema "payment" do
    field(:amount, :integer)
    field(:refunded_amount, :integer)
    field(:captured_amount, :integer)
    field(:interest, :integer)
    field(:company_id, :integer)
    field(:installments, :integer)
    field(:application_id, :string)
    field(:external_id, :string)
    field(:soft_descriptor, :string, default: nil)
    field(:postback_url, :string, default: nil)
    field(:capture, :boolean, default: false)

    has_one(:details, Details)
    has_one(:declined_reason, DeclinedReason)
    has_one(:antifraud_data, AntifraudData)
    has_one(:bureau_decision, BureauDecision)
    has_one(:external_authorization, ExternalAuthorization)

    has_many(:items, Item)
    has_many(:antifraud_decision, AntifraudDecision)
    has_many(:status_history, StatusHistory)
    has_many(:refund_history, RefundHistory)
    has_many(:capture_history, CaptureHistory)
    has_many(:postback_history, PostbackHistory)

    belongs_to(:currency, Currency)
    belongs_to(:type, Type)
    belongs_to(:acquirer, Acquirer)
    belongs_to(:customer, Customer)
    belongs_to(:credit_card, CreditCard, type: :binary_id)

    belongs_to(:bank_billet, BankBillet)
    belongs_to(:paypal, PayPal, type: :binary_id)
    belongs_to(:qrcode, QRCode, type: :binary_id)
    belongs_to(:status, Status)
    belongs_to(:acquirer_config, AcquirerConfig, type: :binary_id)
    belongs_to(:antifraud_config, AntifraudConfig, type: :binary_id)
    belongs_to(:antifraud, Antifraud)

    timestamps(inserted_at: :created_at, type: :utc_datetime)
  end

  @required_fields ~w(
    amount
    currency_id
    external_id
    company_id
    application_id
    customer_id
    acquirer_id
    interest
    status_id
  )a

  @optional_fields ~w(
    credit_card_id
    external_authorization_id
    bank_billet_id
    paypal_id
    acquirer_config_id
    soft_descriptor
    postback_url
    installments
    antifraud_id
    antifraud_config_id
    type_id
    capture
    refunded_amount
    captured_amount
  )a

  @doc false
  def changeset(%__MODULE__{} = payment, attrs) do
    payment
    |> cast(attrs, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
  end

  @doc false
  def new(attrs) do
    __MODULE__.changeset(%__MODULE__{}, attrs)
  end

  @doc """
  Check if payment can receive postback
  """
  def can_process_postback?(%{status: %{name: name}}, new_status) when name == new_status,
    do: false

  def can_process_postback?(%{status: %{name: name}}, new_status) when name == "manual review" do
    if new_status != "canceled" do
      false
    else
      true
    end
  end

  def can_process_postback?(_status, _new_status), do: true

  @doc """
  Check if payment can be captured
  """
  def can_capture?(%__MODULE__{} = payment) do
    allow_capture = ["authorized", "manual review", "generated"]

    if Enum.member?(allow_capture, payment.status.name) do
      true
    else
      false
    end
  end

  @doc """
  Check if payment can be refunded
  """
  def can_refund?(%__MODULE__{} = payment) do
    allow_refund = ["authorized", "manual review", "paid"]

    if Enum.member?(allow_refund, payment.status.name) do
      true
    else
      false
    end
  end

  @doc """
  Check if payment is in manual review
  """
  def in_analysis?(%__MODULE__{} = payment) do
    if payment.status.name == "manual review" do
      true
    else
      false
    end
  end

  @doc """
  Check if payment has a status history
  """
  def has_status_history?(payment, status) do
    sh_query =
      from(sh in StatusHistory,
        where:
          sh.status_id == ^Status.get_id(status) and
            sh.payment_id == ^payment.id
      )

    if Repo.one(sh_query), do: true, else: false
  end

  def to_map(payment) when is_nil(payment), do: nil

  def to_map(%__MODULE__{} = payment) do
    %{
      id: payment.id,
      type: payment.type.name,
      externalId: payment.external_id,
      softDescriptor: payment.soft_descriptor,
      amount: payment.amount,
      interest: payment.interest,
      currency: payment.currency.iso,
      capture: payment.capture,
      capturedAmount: payment.captured_amount,
      companyId: payment.company_id,
      applicationId: payment.application_id,
      refundedAmount: payment.refunded_amount,
      status: payment.status.name,
      createdAt: payment.created_at,
      updatedAt: payment.updated_at,
      postbackUrl: payment.postback_url,
      installments: payment.installments,
      customer: payment.customer |> Customer.to_map(),
      creditCard: payment.credit_card |> CreditCard.to_map(),
      externalAuthorization: payment.external_authorization |> ExternalAuthorization.to_map(),
      details: payment.details |> Details.to_map(),
      bankBillet: payment.bank_billet |> BankBillet.to_map(),
      paypal: payment.paypal |> PayPal.to_map(),
      qrcode: payment.qrcode |> QRCode.to_map(),
      declinedReason: DeclinedReason.to_map(payment.declined_reason, payment.acquirer_id),
      statusHistory: StatusHistory.to_map(payment.status_history),
      refundHistory: RefundHistory.to_map(payment.refund_history),
      captureHistory: CaptureHistory.to_map(payment.capture_history),
      items: Enum.map(payment.items, &Item.to_map(&1)),
      acquirer:
        Acquirer.to_map(
          payment.acquirer,
          payment.acquirer_config
        ),
      antifraud:
        Antifraud.to_map(
          payment.antifraud,
          payment.antifraud_data,
          payment.antifraud_decision
        )
    }
  end
end

has_one is not the right association to use here - it expects the foreign key on the OTHER schema (so it would be looking for a payment_id column on external_authorization).

belongs_to :external_authorization, ExternalAuthorization will correctly declare the external_authorization_id field on payment.