lib/stripe/core_resources/charge.ex

defmodule Stripe.Charge do
  @moduledoc """
  Work with [Stripe `charge` objects](https://stripe.com/docs/api/charges).

  You can:
  - [Create a charge](https://stripe.com/docs/api/charges/create)
  - [Retrieve a charge](https://stripe.com/docs/api/charges/retrieve)
  - [Update a charge](https://stripe.com/docs/api/charges/update)
  - [Capture a charge](https://stripe.com/docs/api/charges/capture)
  - [List all charges](https://stripe.com/docs/api/charges/list)
  - [Search charges] (https://stripe.com/docs/api/charges/search)
  """

  use Stripe.Entity
  import Stripe.Request
  require Stripe.Util

  @type user_fraud_report :: %{
          user_report: String.t()
        }

  @type stripe_fraud_report :: %{
          stripe_report: String.t()
        }

  @type charge_outcome :: %{
          network_status: String.t() | nil,
          reason: String.t() | nil,
          risk_level: String.t(),
          rule: Stripe.id() | charge_outcome_rule,
          seller_message: String.t() | nil,
          type: String.t()
        }

  @type charge_outcome_rule :: %{
          action: String.t(),
          id: String.t(),
          predicate: String.t()
        }

  @type billing_details :: %{
          email: String.t(),
          address: String.t() | nil,
          name: String.t(),
          phone: String.t() | nil
        }

  @type card_info :: %{
          exp_month: number,
          exp_year: number,
          number: String.t(),
          object: String.t(),
          cvc: String.t(),
          address_city: String.t() | nil,
          address_country: String.t() | nil,
          address_line1: String.t() | nil,
          address_line2: String.t() | nil,
          name: String.t() | nil,
          address_state: String.t() | nil,
          address_zip: String.t() | nil
        }

  @type transfer_data :: %{
          :amount => non_neg_integer,
          :destination => String.t()
        }

  @type t :: %__MODULE__{
          id: Stripe.id(),
          object: String.t(),
          amount: non_neg_integer,
          amount_captured: non_neg_integer,
          amount_refunded: non_neg_integer,
          application: Stripe.id() | nil,
          application_fee: Stripe.id() | Stripe.ApplicationFee.t() | nil,
          application_fee_amount: Stripe.id() | Stripe.ApplicationFee.t() | nil,
          balance_transaction: Stripe.id() | Stripe.BalanceTransaction.t() | nil,
          billing_details: billing_details | nil,
          calculated_statement_descriptor: String.t() | nil,
          captured: boolean,
          created: Stripe.timestamp(),
          currency: String.t(),
          customer: Stripe.id() | Stripe.Customer.t() | nil,
          description: String.t() | nil,
          disputed: boolean,
          failure_code: Stripe.Error.card_error_code() | nil,
          failure_message: String.t() | nil,
          fraud_details: user_fraud_report | stripe_fraud_report | %{},
          invoice: Stripe.id() | Stripe.Invoice.t() | nil,
          livemode: boolean,
          metadata: Stripe.Types.metadata(),
          on_behalf_of: Stripe.id() | Stripe.Account.t() | nil,
          order: Stripe.id() | Stripe.Order.t() | nil,
          outcome: charge_outcome | nil,
          paid: boolean,
          payment_intent: Stripe.id() | Stripe.PaymentIntent.t() | nil,
          payment_method: Stripe.id() | Stripe.PaymentMethod.t() | nil,
          payment_method_details: map,
          receipt_email: String.t() | nil,
          receipt_number: String.t() | nil,
          receipt_url: String.t() | nil,
          refunded: boolean,
          refunds: Stripe.List.t(Stripe.Refund.t()),
          review: Stripe.id() | Stripe.Review.t() | nil,
          shipping: Stripe.Types.shipping() | nil,
          source_transfer: Stripe.id() | Stripe.Transfer.t() | nil,
          statement_descriptor: String.t() | nil,
          statement_descriptor_suffix: String.t() | nil,
          status: String.t(),
          transfer: Stripe.id() | Stripe.Transfer.t() | nil,
          transfer_data: transfer_data | nil,
          transfer_group: String.t() | nil
        }

  defstruct [
    :id,
    :object,
    :amount,
    :amount_captured,
    :amount_refunded,
    :application,
    :application_fee,
    :application_fee_amount,
    :balance_transaction,
    :billing_details,
    :calculated_statement_descriptor,
    :captured,
    :created,
    :currency,
    :customer,
    :description,
    :disputed,
    :failure_code,
    :failure_message,
    :fraud_details,
    :invoice,
    :livemode,
    :metadata,
    :on_behalf_of,
    :order,
    :outcome,
    :paid,
    :payment_intent,
    :payment_method,
    :payment_method_details,
    :receipt_email,
    :receipt_number,
    :receipt_url,
    :refunded,
    :refunds,
    :review,
    :shipping,
    :source_transfer,
    :statement_descriptor,
    :statement_descriptor_suffix,
    :status,
    :transfer,
    :transfer_data,
    :transfer_group
  ]

  @plural_endpoint "charges"

  @doc """
  Create a charge.

  If your API key is in test mode, the supplied payment source (e.g., card) won't actually be
  charged, though everything else will occur as if in live mode.
  (Stripe assumes that the charge would have completed successfully).

  See the [Stripe docs](https://stripe.com/docs/api/charges/create).
  """
  @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
        when params:
               %{
                 :amount => pos_integer,
                 :currency => String.t(),
                 optional(:application_fee_amount) => non_neg_integer,
                 optional(:capture) => boolean,
                 optional(:customer) => Stripe.id() | Stripe.Customer.t(),
                 optional(:description) => String.t(),
                 optional(:on_behalf_of) => Stripe.id() | Stripe.Account.t(),
                 optional(:metadata) => map,
                 optional(:receipt_email) => String.t(),
                 optional(:shipping) => Stripe.Types.shipping(),
                 optional(:source) => Stripe.id() | Stripe.Card.t() | card_info,
                 optional(:statement_descriptor) => String.t(),
                 optional(:statement_descriptor_suffix) => String.t(),
                 optional(:transfer_data) => transfer_data,
                 optional(:transfer_group) => String.t()
               }
               | %{}
  def create(params, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint)
    |> put_params(params)
    |> put_method(:post)
    |> cast_to_id([:on_behalf_of, :customer, :source])
    |> make_request()
  end

  @doc """
  Retrieve a charge.

  Retrieves the details of a charge that has previously been created.
  Supply the unique charge ID that was returned from your previous request, and Stripe will return
  the corresponding charge information. The same information is returned when creating or refunding
  the charge.

  See the [Stripe docs](https://stripe.com/docs/api/charges/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 a charge.

  Updates the specified charge by setting the values of the parameters passed. Any parameters
  not provided will be left unchanged.

  This request accepts only the `:description`, `:metadata`, `:receipt_email`, `:fraud_details`,
  and `:shipping` as arguments, as well as `:transfer_group` in some cases.

  The charge to be updated may either be passed in as a struct or an ID.

  See the [Stripe docs](https://stripe.com/docs/api/charges/update).
  """
  @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
        when params:
               %{
                 optional(:customer) => Stripe.id() | Stripe.Customer.t(),
                 optional(:description) => String.t(),
                 optional(:fraud_details) => user_fraud_report,
                 optional(:metadata) => Stripe.Types.metadata(),
                 optional(:receipt_email) => String.t(),
                 optional(:shipping) => Stripe.Types.shipping(),
                 optional(:transfer_group) => 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 """
  Capture a charge.

  Capture the payment of an existing, uncaptured, charge. This is the second
  half of the two-step payment flow, where first you created a charge with the
  capture option set to false.

  Uncaptured payments expire exactly seven days after they are created. If they
  are not captured by that point in time, they will be marked as refunded and
  will no longer be capturable.

  See the [Stripe docs](https://stripe.com/docs/api/charges/capture).
  """
  @spec capture(Stripe.id() | t, params, Stripe.options()) ::
          {:ok, t} | {:error, Stripe.Error.t()}
        when params: %{
               optional(:amount) => non_neg_integer,
               optional(:application_fee_amount) => non_neg_integer,
               optional(:receipt_email) => String.t(),
               optional(:statement_descriptor) => String.t(),
               optional(:statement_descriptor_suffix) => String.t(),
               optional(:transfer_data) => transfer_data,
               optional(:transfer_group) => String.t()
             }
  def capture(id, params, opts) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/capture")
    |> put_params(params)
    |> put_method(:post)
    |> make_request()
  end

  @doc """
  [DEPRECATED] Capture a charge.

  This version of the function is deprecated. Please use `capture/3` instead.
  """
  @spec capture(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
  def capture(id, opts) when is_list(opts) do
    Stripe.Util.log_deprecation("Please use `capture/3` instead.")
    capture(id, %{}, opts)
  end

  @spec capture(Stripe.id() | t, map) :: {:ok, t} | {:error, Stripe.Error.t()}
  def capture(id, params) when is_map(params) do
    capture(id, params, [])
  end

  @spec capture(Stripe.id() | t) :: {:ok, t} | {:error, Stripe.Error.t()}
  def capture(id) do
    Stripe.Util.log_deprecation("Please use `capture/3` instead.")
    capture(id, %{}, [])
  end

  @doc """
  List all charges.

  Returns a list of charges you’ve previously created. The charges are returned in sorted order,
  with the most recent charges appearing first.

  See the [Stripe docs](https://stripe.com/docs/api/charges/list).
  """
  @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()}
        when params: %{
               optional(:created) => Stripe.date_query(),
               optional(:customer) => Stripe.Customer.t() | Stripe.id(),
               optional(:ending_before) => t | Stripe.id(),
               optional(:limit) => 1..100,
               optional(:source) => %{
                 optional(:object) => String.t()
               },
               optional(:starting_after) => t | Stripe.id(),
               optional(:transfer_group) => String.t(),
               optional(:payment_intent) => Stripe.PaymentIntent.t() | Stripe.id()
             }
  def list(params \\ %{}, opts \\ []) do
    new_request(opts)
    |> prefix_expansions()
    |> put_endpoint(@plural_endpoint)
    |> put_method(:get)
    |> put_params(params)
    |> cast_to_id([:customer, :ending_before, :starting_after])
    |> make_request()
  end

  @doc """
  Search charges

  See the [Stripe docs](https://stripe.com/docs/api/charges/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
end