lib/stripe/subscriptions/invoice.ex

defmodule Stripe.Invoice do
  @moduledoc """
  Work with Stripe invoice objects.

  You can:

  - Create an invoice
  - Retrieve an invoice
  - Update an invoice
  - Void an invoice
  - Search invoices

  Does not take options yet.

  Stripe API reference: https://stripe.com/docs/api#invoice
  """

  use Stripe.Entity
  import Stripe.Request

  @type t :: %__MODULE__{
          id: Stripe.id(),
          object: String.t(),
          account_country: String.t(),
          account_name: String.t(),
          account_tax_ids: list(String.t()),
          amount_due: integer,
          amount_paid: integer,
          amount_remaining: integer,
          application_fee_amount: integer | nil,
          attempt_count: non_neg_integer,
          attempted: boolean,
          auto_advance: boolean,
          automatic_tax: map,
          billing_reason: String.t() | nil,
          charge: Stripe.id() | Stripe.Charge.t() | nil,
          collection_method: String.t() | nil,
          created: Stripe.timestamp(),
          currency: String.t(),
          custom_fields: custom_fields() | nil,
          customer: Stripe.id() | Stripe.Customer.t(),
          customer_address: Stripe.Types.address() | nil,
          customer_email: String.t() | nil,
          customer_name: String.t() | nil,
          customer_phone: String.t() | nil,
          customer_shipping: Stripe.Types.shipping() | nil,
          customer_tax_exempt: String.t() | nil,
          customer_tax_ids: Stripe.List.t(map) | nil,
          default_payment_method: String.t() | nil,
          default_source: String.t() | nil,
          default_tax_rates: Stripe.List.t(map) | nil,
          deleted: boolean | nil,
          description: String.t() | nil,
          discount: Stripe.Discount.t() | nil,
          discounts: list(String.t()),
          due_date: Stripe.timestamp() | nil,
          ending_balance: integer | nil,
          footer: String.t() | nil,
          from_invoice: from_invoice() | nil,
          hosted_invoice_url: String.t() | nil,
          invoice_pdf: String.t() | nil,
          last_finalization_error: map,
          lines: Stripe.List.t(Stripe.LineItem.t()),
          livemode: boolean,
          metadata: Stripe.Types.metadata() | nil,
          next_payment_attempt: Stripe.timestamp() | nil,
          number: String.t() | nil,
          on_behalf_of: String.t(),
          paid: boolean,
          payment_intent: String.t() | nil,
          payment_settings: map,
          period_end: Stripe.timestamp(),
          period_start: Stripe.timestamp(),
          post_payment_credit_notes_amount: integer,
          pre_payment_credit_notes_amount: integer,
          receipt_number: String.t() | nil,
          starting_balance: integer,
          statement_descriptor: String.t() | nil,
          status: String.t() | nil,
          status_transitions: status_transitions() | nil,
          subscription: Stripe.id() | Stripe.Subscription.t() | nil,
          subscription_proration_date: Stripe.timestamp() | nil,
          subtotal: integer,
          tax: integer | nil,
          tax_rate: Stripe.id() | Stripe.TaxRate.t() | nil,
          threshold_reason:
            nil
            | %{
                amount_gte: integer,
                item_reasons: [
                  %{
                    line_item_ids: [String.t()],
                    usage_gte: integer
                  }
                ]
              },
          total: integer,
          total_discount_amounts: Stripe.List.t(map) | nil,
          total_tax_amounts: Stripe.List.t(map) | nil,
          transfer_data: map,
          webhooks_delivered_at: Stripe.timestamp() | nil
        }

  @type custom_fields ::
          list(%{
            name: String.t(),
            value: String.t()
          })

  @type invoice_settings :: %{
          optional(:default_payment_method) => String.t() | Stripe.PaymentMethod.t(),
          optional(:custom_fields) => custom_fields,
          optional(:footer) => String.t()
        }

  @type status_transitions ::
          list(%{
            finalized_at: Stripe.timestamp() | nil,
            marked_uncollectible_at: Stripe.timestamp() | nil,
            paid_at: Stripe.timestamp() | nil,
            voided_at: Stripe.timestamp() | nil
          })

  @type from_invoice :: %{
          action: String.t(),
          invoice: Stripe.id()
  }

  defstruct [
    :id,
    :object,
    :account_country,
    :account_name,
    :account_tax_ids,
    :amount_due,
    :amount_paid,
    :amount_remaining,
    :application_fee_amount,
    :attempt_count,
    :attempted,
    :auto_advance,
    :automatic_tax,
    :billing_reason,
    :charge,
    :collection_method,
    :created,
    :currency,
    :custom_fields,
    :customer,
    :customer_address,
    :customer_email,
    :customer_name,
    :customer_phone,
    :customer_shipping,
    :customer_tax_exempt,
    :customer_tax_ids,
    :default_payment_method,
    :default_source,
    :default_tax_rates,
    :deleted,
    :description,
    :discount,
    :discounts,
    :due_date,
    :ending_balance,
    :footer,
    :from_invoice,
    :hosted_invoice_url,
    :invoice_pdf,
    :last_finalization_error,
    :lines,
    :livemode,
    :metadata,
    :next_payment_attempt,
    :number,
    :on_behalf_of,
    :paid,
    :payment_intent,
    :payment_settings,
    :period_end,
    :period_start,
    :post_payment_credit_notes_amount,
    :pre_payment_credit_notes_amount,
    :receipt_number,
    :starting_balance,
    :statement_descriptor,
    :status,
    :status_transitions,
    :subscription,
    :subscription_proration_date,
    :subtotal,
    :tax,
    :tax_rate,
    :threshold_reason,
    :total,
    :total_discount_amounts,
    :total_tax_amounts,
    :transfer_data,
    :webhooks_delivered_at
  ]

  @plural_endpoint "invoices"

  @doc """
  Create an invoice

  This endpoint creates a draft invoice for a given customer. The draft invoice
  created pulls in all pending invoice items on that customer, including
  prorations.

  See [Stripe docs](https://stripe.com/docs/api/invoices/update)
  """
  @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
        when params:
               %{
                 optional(:account_tax_ids) => list(String.t()),
                 optional(:application_fee_amount) => integer,
                 optional(:auto_advance) => boolean,
                 optional(:automatic_tax) => map,
                 optional(:collection_method) => String.t(),
                 :customer => Stripe.id() | Stripe.Customer.t(),
                 optional(:custom_fields) => custom_fields,
                 optional(:days_until_due) => integer,
                 optional(:default_payment_method) => String.t(),
                 optional(:default_source) => String.t(),
                 optional(:default_tax_rates) => [Stripe.id()],
                 optional(:description) => String.t(),
                 optional(:discounts) => list(String.t()),
                 optional(:due_date) => Stripe.timestamp(),
                 optional(:footer) => String.t(),
                 optional(:metadata) => Stripe.Types.metadata(),
                 optional(:payment_settings) => map,
                 optional(:statement_descriptor) => String.t(),
                 optional(:subscription) => Stripe.id() | Stripe.Subscription.t()
               }
               | %{}
  def create(params, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint)
    |> put_params(params)
    |> put_method(:post)
    |> cast_to_id([:subscription])
    |> make_request()
  end

  @doc """
  Retrieve an invoice.

  Retrieves the invoice with the given ID.

  See [Stripe docs](https://stripe.com/docs/api/invoices/retrieve)
  """
  @spec retrieve(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
  def retrieve(id, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}")
    |> put_method(:get)
    |> make_request()
  end

  @doc """
  Update an invoice.

  Takes the `id` and a map of changes. Draft invoices are fully editable. Once
  an invoice is finalized, monetary values, as well as collection_method, become
  uneditable.

  See [Stripe docs](https://stripe.com/docs/api/invoices/update)
  """
  @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
        when params:
               %{
                 optional(:account_tax_ids) => list(String.t()),
                 optional(:application_fee_amount) => integer,
                 optional(:auto_advance) => boolean,
                 optional(:automatic_tax) => map,
                 optional(:custom_fields) => custom_fields,
                 optional(:days_until_due) => integer,
                 optional(:default_payment_method) => String.t(),
                 optional(:default_source) => String.t(),
                 optional(:default_tax_rates) => [Stripe.id()],
                 optional(:description) => String.t(),
                 optional(:discounts) => list(String.t()),
                 optional(:due_date) => Stripe.timestamp(),
                 optional(:footer) => String.t(),
                 optional(:forgiven) => boolean,
                 optional(:metadata) => Stripe.Types.metadata(),
                 optional(:paid) => boolean,
                 optional(:payment_settings) => map,
                 optional(:statement_descriptor) => String.t()
               }
               | %{}
  def update(id, params, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}")
    |> put_method(:post)
    |> put_params(params)
    |> make_request()
  end

  @doc """
  Retrieve an upcoming invoice

  At any time, you can preview the upcoming invoice for a customer. This will
  show you all the charges that are pending, including subscription renewal
  charges, invoice item charges, etc. It will also show you any discount that is
  applicable to the customer.

  See [Stripe docs](https://stripe.com/docs/api/invoices/upcoming)
  """
  @spec upcoming(map, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
  def upcoming(params, opts \\ [])
  def upcoming(params = %{customer: _customer}, opts), do: get_upcoming(params, opts)

  def upcoming(params = %{customer_details: _customer_details}, opts),
    do: get_upcoming(params, opts)

  def upcoming(params = %{subscription: _subscription}, opts), do: get_upcoming(params, opts)

  defp get_upcoming(params, opts) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint <> "/upcoming")
    |> put_method(:get)
    |> put_params(params)
    |> make_request()
  end

  @doc """
  List all invoices

  You can list all invoices, or list the invoices for a specific customer. The
  invoices are returned sorted by creation date, with the most recently created
  invoices appearing first.

  See [Stripe docs](https://stripe.com/docs/api/invoices/list)
  """
  @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()}
        when params:
               %{
                 optional(:collection_method) => String.t(),
                 optional(:customer) => Stripe.id() | Stripe.Customer.t(),
                 optional(:created) => Stripe.date_query(),
                 optional(:due_date) => Stripe.timestamp(),
                 optional(:ending_before) => t | Stripe.id(),
                 optional(:limit) => 1..100,
                 optional(:starting_after) => t | Stripe.id(),
                 optional(:subscription) => Stripe.id() | Stripe.Subscription.t(),
                 optional(:status) => String.t()
               }
               | %{}
  def list(params \\ %{}, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint)
    |> put_method(:get)
    |> put_params(params)
    |> cast_to_id([:customer, :ending_before, :starting_after, :subscription])
    |> make_request()
  end

  @doc """
  Search invoices

  See the [Stripe docs](https://stripe.com/docs/api/invoices/search).
  """
  @spec search(params, Stripe.options()) ::
          {:ok, Stripe.SearchResult.t(t)} | {:error, Stripe.Error.t()}
        when params: %{
               :query => Stripe.search_query(),
               optional(:limit) => 1..100,
               optional(:page) => String.t()
             }
  def search(params, opts \\ []) do
    new_request(opts)
    |> prefix_expansions()
    |> put_endpoint(@plural_endpoint <> "/search")
    |> put_method(:get)
    |> put_params(params)
    |> make_request()
  end

  @doc """
  Finalize an invoice

  Stripe automatically finalizes drafts before sending and attempting payment on
  invoices. However, if you’d like to finalize a draft invoice manually, you can
  do so using this method.

  See [Stripe docs](https://stripe.com/docs/api/invoices/finalize)
  """
  @spec finalize(Stripe.id() | t, params, Stripe.options()) ::
          {:ok, t} | {:error, Stripe.Error.t()}
        when params:
               %{
                 :id => String.t(),
                 optional(:auto_advance) => boolean
               }
               | %{}
  def finalize(id, params, opts \\ []) do
    new_request(opts)
    |> prefix_expansions()
    |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/finalize")
    |> put_method(:post)
    |> put_params(params)
    |> cast_to_id([:source])
    |> make_request()
  end

  @doc """
  Pay an invoice

  Stripe automatically creates and then attempts to collect payment on invoices
  for customers on subscriptions according to your subscriptions settings.
  However, if you’d like to attempt payment on an invoice out of the normal
  collection schedule or for some other reason, you can do so.

  See [Stripe docs](https://stripe.com/docs/api/invoices/delete)
  """
  @spec pay(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
        when params:
               %{
                 :id => String.t(),
                 optional(:forgive) => boolean,
                 optional(:paid_out_of_band) => boolean,
                 optional(:payment_method) => String.t(),
                 optional(:source) => Stripe.id() | Stripe.Source.t(),
                 optional(:off_session) => boolean
               }
               | %{}
  def pay(id, params, opts \\ []) do
    new_request(opts)
    |> prefix_expansions()
    |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/pay")
    |> put_method(:post)
    |> put_params(params)
    |> cast_to_id([:source])
    |> make_request()
  end

  @doc """
  Void an invoice

  Mark a finalized invoice as void. This cannot be undone. Voiding an invoice is
  similar to deletion, however it only applies to finalized invoices and
  maintains a papertrail where the invoice can still be found.

  See [Stripe docs](https://stripe.com/docs/api/invoices/void)
  """
  @spec void(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
  def void(id, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/void")
    |> put_method(:post)
    |> make_request()
  end

  @doc """
  Send an invoice

  Stripe will automatically send invoices to customers according to your
  subscriptions settings. However, if you’d like to manually send an invoice to
  your customer out of the normal schedule, you can do so. When sending invoices
  that have already been paid, there will be no reference to the payment in the
  email.

  Requests made in test-mode result in no emails being sent, despite sending an
  `invoice.sent` event.


  See [Stripe docs](https://stripe.com/docs/api/invoices/send)
  """
  @spec send(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
  def send(id, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/send")
    |> put_method(:post)
    |> make_request()
  end

  @doc """
  Delete an invoice

  Permanently deletes a draft invoice. This cannot be undone. Attempts to delete
  invoices that are no longer in a draft state will fail; once an invoice has
  been finalized, it must be voided.

  ## Example

      {:ok, _} = Stripe.Invoice.delete("in_16vEXC2eZvKYlo2CU9MyflAA")

      {:ok, _} = Stripe.Invoice.delete(%Stripe.Invoice{id: "in_16vEXC2eZvKYlo2CU9MyflAA"})

  See [Stripe docs](https://stripe.com/docs/api/invoices/delete)
  """
  @spec delete(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
  def delete(id, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}")
    |> put_method(:delete)
    |> make_request()
  end

  @doc """
  Mark an invoice as uncollectible

  Marking an invoice as uncollectible is useful for keeping track of bad debts
  that can be written off for accounting purposes.

  ## Example

      {:ok, _} = Stripe.Invoice.mark_as_uncollectible("in_16vEXC2eZvKYlo2CU9MyflAA")

      {:ok, _} = Stripe.Invoice.mark_as_uncollectible(%Stripe.Invoice{id: "in_16vEXC2eZvKYlo2CU9MyflAA"})

  See [Stripe docs](https://stripe.com/docs/api/invoices/mark_uncollectible)
  """
  @spec mark_as_uncollectible(Stripe.id() | t, Stripe.options()) ::
          {:ok, t} | {:error, Stripe.Error.t()}
  def mark_as_uncollectible(id, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/mark_uncollectible")
    |> put_method(:post)
    |> make_request()
  end
end