lib/requests/payments/payment.ex

defmodule Requests.Payments.Payment do
  @moduledoc """
  TODO: either implement all of the sources or none and return the api errors.
  """
  alias Customers.BillingAddress
  alias Customers.Phone
  alias Requests.Payments.Payment.{
    AirlineData,
    AmountAllocation,
    BillingDescriptor,
    Risk,
    Sender
  }

  alias Requests.Payments.Sources
  alias Requests.Payments.ThreeDS

  @derive Jason.Encoder

  @type customer :: %{
          id: String.t(),
          email: String.t(),
          name: String.t(),
          tax_number: String.t(),
          phone: Phone.t()
        }

  @type item :: %{
          name: String.t(),
          quantity: String.t(),
          unit_price: String.t(),
          reference: String.t(),
          commodity_code: String.t(),
          unit_of_measure: String.t(),
          total_amount: String.t(),
          tax_amount: String.t(),
          discount_amount: String.t(),
          wxpay_goods_id: String.t(),
          url: String.t(),
          image_url: String.t()
        }

  @type processing :: %{
          order_id: String.t(),
          tax_amount: integer(),
          discount_amount: integer(),
          duty_amount: integer(),
          shipping_amount: integer(),
          shipping_tax_amount: integer(),
          aft: true | false,
          preferred_scheme: String.t(),
          merchant_initiated_reason: String.t(),
          campaign_id: integer(),
          product_type: String.t(),
          open_id: String.t(),
          original_order_amount: integer(),
          receipt_id: String.t(),
          terminal_type: String.t(),
          os_type: String.t(),
          invoice_id: String.t(),
          brand_name: String.t(),
          locale: String.t(),
          shipping_preference: String.t(),
          user_action: String.t(),
          set_transaction_context: map(),
          airline_data: AirlineData.t(),
          purchase_country: String.t(),
          custom_payment_method_ids: list(String.t()),
          merchant_callback_url: String.t()
        }

  @type recipient :: %{
          dob: String.t(),
          account_number: String.t(),
          address: BillingAddress.t(),
          first_name: String.t(),
          last_name: String.t()
        }

  @type shipping :: %{
          address: BillingAddress.t(),
          phone: Phone.t(),
          from_address_zip: String.t()
        }

  @type t() :: %__MODULE__{
          source: map(),
          amount: integer(),
          currency: <<_::3>>,
          payment_type: String.t(),
          merchant_initiated: true | false,
          reference: String.t(),
          description: String.t(),
          authorization_type: String.t(),
          capture: true | false,
          capture_on: String.t(),
          customer: customer(),
          billing_descriptor: BillingDescriptor.t(),
          shipping: shipping(),
          three_d_s: Integrated.t() | StandAlone.t() | ThirdParty.t(),
          processing_channel_id: String.t(),
          previous_payment_id: String.t(),
          risk: Risk.t(),
          success_url: String.t(),
          failure_url: String.t(),
          payment_ip: String.t(),
          sender: Sender.t(),
          recipient: recipient(),
          amount_allocations: list(AmountAllocation.t()),
          processing: processing(),
          items: list(item()),
          metadata: map(),
          type: String.t()
        }

  @enforce_keys [:currency, :processing_channel_id, :source, :type]
  defstruct [
    :source,
    :amount,
    :currency,
    :payment_type,
    :merchant_initiated,
    :reference,
    :description,
    :authorization_type,
    :capture,
    :capture_on,
    :customer,
    :billing_descriptor,
    :shipping,
    :three_d_s,
    :"3ds",
    :processing_channel_id,
    :previous_payment_id,
    :risk,
    :success_url,
    :failure_url,
    :payment_ip,
    :sender,
    :recipient,
    :amount_allocations,
    :processing,
    :items,
    :metadata,
    :type
  ]

  def build(
        %{
          currency: currency,
          source: source
        } = params
      )
      when is_binary(currency) and is_map(source) do
    %__MODULE__{
      source: Sources.build(source),
      amount: params[:amount],
      currency: currency,
      payment_type: params[:payment_type],
      merchant_initiated: params[:merchant_initiated],
      reference: params[:reference],
      description: params[:description],
      authorization_type: params[:authorization_type],
      capture: params[:capture],
      capture_on: params[:capture_on],
      customer: build_customer(params[:customer]),
      billing_descriptor: BillingDescriptor.build(params[:billing_descriptor]),
      shipping: build_shipping(params[:shipping]),
      "3ds": ThreeDS.build(params[:three_d_s]),
      processing_channel_id: params[:processing_channel_id],
      previous_payment_id: params[:previous_payment_id],
      risk: Risk.build(params[:risk]),
      success_url: params[:success_url],
      failure_url: params[:failure_url],
      payment_ip: params[:payment_ip],
      sender: Sender.build(params[:sender]),
      recipient: build_recipient(params[:recipient]),
      amount_allocations: AmountAllocation.build(params[:amount_allocations]),
      processing: build_processing(params[:processing]),
      items: build_items(params[:items]),
      metadata: params[:metadata],
      type: params[:type]
    }
  end

  def build(params) when is_map(params),
    do: {:error, "Payment must have currency, processing_channel_id, and source"}

  def build(_), do: {:error, "params must be a map"}

  defp build_customer(params) when is_map(params) do
    %{
      id: params[:id],
      email: params[:email],
      name: params[:name],
      tax_number: params[:tax_number],
      phone: Phone.build(params[:phone])
    }
  end

  defp build_customer(_), do: nil

  defp build_items(items) when is_list(items) do
    Enum.into(items, [], &build_item(&1))
  end

  defp build_items(_), do: nil

  defp build_item(params) when is_map(params) do
    %{
      name: params[:name],
      quantity: params[:quantity],
      unit_price: params[:unit_price],
      reference: params[:reference],
      commodity_code: params[:commodity_code],
      unit_of_measure: params[:unit_of_measure],
      total_amount: params[:total_amount],
      tax_amount: params[:tax_amount],
      discount_amount: params[:discount_amount],
      wxpay_goods_id: params[:wxpay_goods_id],
      url: params[:url],
      image_url: params[:image_url]
    }
  end

  defp build_item(_), do: nil

  defp build_processing(params) when is_map(params) do
    %{
      order_id: params[:order_id],
      tax_amount: params[:tax_amount],
      discount_amount: params[:discount_amount],
      duty_amount: params[:duty_amount],
      shipping_amount: params[:shipping_amount],
      shipping_tax_amount: params[:shipping_tax_amount],
      aft: params[:aft],
      preferred_scheme: params[:preferred_scheme],
      merchant_initiated_reason: params[:merchant_initiated_reason],
      campaign_id: params[:campaign_id],
      product_type: params[:product_type],
      open_id: params[:open_id],
      original_order_amount: params[:original_order_amount],
      receipt_id: params[:receipt_id],
      terminal_type: params[:terminal_type],
      os_type: params[:os_type],
      invoice_id: params[:invoice_id],
      brand_name: params[:brand_name],
      locale: params[:locale],
      shipping_preference: params[:shipping_preference],
      user_action: params[:user_action],
      set_transaction_context: params[:set_transaction_context],
      airline_data: AirlineData.build(params[:airline_data]),
      purchase_country: params[:purchase_country],
      custom_payment_method_ids: params[:custom_payment_method_ids],
      merchant_callback_url: params[:merchant_callback_url]
    }
  end

  defp build_processing(_), do: nil

  defp build_recipient(params) when is_map(params) do
    %{
      dob: params[:dob],
      account_number: params[:account_number],
      address: BillingAddress.build(params[:address]),
      first_name: params[:first_name],
      last_name: params[:last_name]
    }
  end

  defp build_recipient(_), do: nil

  defp build_shipping(params) when is_map(params) do
    %{
      address: BillingAddress.build(params[:address]),
      phone: Phone.build(params[:phone]),
      from_address_zip: params[:from_address_zip]
    }
  end

  defp build_shipping(_), do: nil
end