lib/stripe/checkout/session.ex

defmodule Stripe.Session do
  @moduledoc """
  Work with Stripe Checkout Session objects.

  You can:

  - Create a new session
  - Retrieve a session

  Stripe API reference: https://stripe.com/docs/api/checkout/sessions
  """

  use Stripe.Entity
  import Stripe.Request

  @type customer_update_param :: %{
          optional(:address) => String.t(),
          optional(:name) => String.t(),
          optional(:shipping) => String.t()
        }

  @type discount_param :: %{
          optional(:coupon) => String.t(),
          optional(:promotion_code) => String.t()
        }

  @type setup_intent_data :: %{
          optional(:description) => String.t(),
          optional(:metadata) => Stripe.Types.metadata(),
          optional(:on_behalf_of) => String.t()
        }

  @type shipping_address_collection :: %{
          allowed_countries: [String.t()]
        }

  @typedoc """
  One of `"auto"` or `"required"`
  """
  @type billing_address_collection :: String.t()

  @typedoc """
  For sessions in `payment` mode only.
  One of `"auto"`, `"pay"`, `"book"`, or `"donate"`.
  """
  @type submit_type :: String.t()

  @type breakdown_discount :: %{
          amount: integer(),
          discount: Stripe.Discount.t()
        }

  @type breakdown_tax :: %{
          amount: integer(),
          rate: Stripe.TaxID.t()
        }

  @type total_details :: %{
          :amount_discount => integer(),
          :amount_shipping => integer(),
          :amount_tax => integer(),
          optional(:breakdown) => %{
            discounts: [breakdown_discount()],
            taxes: [breakdown_tax()]
          }
        }

  @type tax_id_collection :: %{
          enabled: boolean()
        }

  @typedoc """
  One of `"personal"` or  "business"`.
  """
  @type acss_mandate_transaction_type :: String.t()

  @typedoc """
  One of `"interval"`, `"sporadic"`, or  "combined"`.
  """
  @type acss_mandate_payment_schedule :: String.t()

  @type acss_mandate_options :: %{
          url: String.t(),
          default_for: [String.t()],
          interval_description: String.t() | nil,
          payment_schedule: acss_mandate_payment_schedule() | nil,
          transaction_type: acss_mandate_transaction_type()
        }

  @typedoc """
  One of `"automatic"`, `"instant"`, or  "microdeposits"`.
  """
  @type acss_verification_method :: String.t()

  @type acss_debit :: %{
          currency: String.t() | nil,
          mandate_options: acss_mandate_options() | nil,
          verification_method: acss_verification_method()
        }

  @type boleto :: %{
          expires_after_days: non_neg_integer() | nil
        }

  @type oxxo :: %{
          expires_after_days: non_neg_integer() | nil
        }

  @type payment_method_options :: %{
          acss_debit: acss_debit() | nil,
          boleto: boleto() | nil,
          oxxo: oxxo() | nil
        }

  @typedoc """
  One of `"if_required"`, or `"always"`.
  """
  @type customer_creation :: String.t()

  @type customer_details :: %{
          email: String.t() | nil,
          tax_exempt: String.t() | nil,
          tax_ids: [Stripe.TaxID.tax_id_data()]
        }

  @type consent :: %{
          promotions: String.t()
        }

  @type consent_collection :: %{
          promotions: String.t(),
          terms_of_service: String.t()
        }

  @typedoc """
  One of `"requires_location_inputs"`, `"complete"`, `"failed"`.
  """
  @type automatic_tax_status :: String.t()

  @type automatic_tax :: %{
          enabled: boolean(),
          status: automatic_tax_status() | nil
        }

  @type automatic_tax_param :: %{
          enabled: boolean()
        }

  @type expiration :: %{
          optional(:recovery) => %{
            optional(:allow_promotion_codes) => boolean(),
            optional(:enabled) => boolean(),
            :expires_at => Stripe.timestamp(),
            :url => String.t()
          }
        }

  @type line_item_data :: %{
          optional(:id) => Stripe.id(),
          optional(:object) => String.t(),
          optional(:quantity) => integer(),
          optional(:amount_discount) => integer(),
          optional(:amount_subtotal) => integer(),
          optional(:amount_tax) => integer(),
          optional(:amount_total) => integer(),
          optional(:currency) => String.t(),
          optional(:description) => String.t(),
          optional(:discounts) => list(map),
          optional(:dynamic_tax_rates) => list(String.t()),
          optional(:price) => String.t(),
          optional(:price_data) => price_data,
          optional(:taxes) => list(map)
        }

  @type line_item :: %{
          optional(:object) => String.t(),
          optional(:data) => line_item_data(),
          optional(:has_more) => boolean,
          optional(:url) => String.t()
        }

  @type adjustable_quantity :: %{
          :enabled => boolean(),
          optional(:maximum) => integer(),
          optional(:minimum) => integer()
        }

  @type price_data :: %{
          :currency => String.t(),
          optional(:product) => String.t(),
          optional(:product_data) => product_data(),
          optional(:unit_amount) => integer(),
          optional(:unit_amount_decimal) => integer(),
          optional(:recurring) => recurring()
        }

  @type product_data :: %{
          :name => String.t(),
          optional(:description) => String.t(),
          optional(:images) => list(String.t()),
          optional(:metadata) => Stripe.Types.metadata()
        }

  @type recurring :: %{
          :interval => String.t(),
          :interval_count => integer()
        }

  @type capture_method :: :automatic | :manual

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

  @type payment_intent_data :: %{
          optional(:application_fee_amount) => integer(),
          optional(:capture_method) => capture_method,
          optional(:description) => String.t(),
          optional(:metadata) => Stripe.Types.metadata(),
          optional(:on_behalf_of) => String.t(),
          optional(:receipt_email) => String.t(),
          optional(:setup_future_usage) => String.t(),
          optional(:shipping) => Stripe.Types.shipping(),
          optional(:statement_descriptor) => String.t(),
          optional(:transfer_data) => transfer_data
        }

  @type item :: %{
          :plan => String.t(),
          optional(:quantity) => integer()
        }

  @type subscription_data :: %{
          optional(:items) => list(item),
          optional(:application_fee_percent) => float(),
          optional(:coupon) => String.t(),
          optional(:default_tax_rates) => list(String.t()),
          optional(:metadata) => Stripe.Types.metadata(),
          optional(:trial_end) => integer(),
          optional(:trial_from_plan) => boolean(),
          optional(:trial_period_days) => integer()
        }

  @type create_params :: %{
          :cancel_url => String.t(),
          optional(:payment_method_types) => list(String.t()),
          :success_url => String.t(),
          optional(:mode) => String.t(),
          optional(:client_reference_id) => String.t(),
          optional(:currency) => String.t(),
          optional(:customer) => String.t(),
          optional(:customer_email) => String.t(),
          optional(:line_items) => list(line_item_data()),
          optional(:locale) => String.t(),
          optional(:metadata) => Stripe.Types.metadata(),
          optional(:after_expiration) => expiration(),
          optional(:allow_promotion_codes) => boolean(),
          optional(:automatic_tax) => automatic_tax_param(),
          optional(:consent_collection) => consent_collection(),
          optional(:customer_update) => customer_update_param(),
          optional(:discounts) => [discount_param()],
          optional(:expires_at) => Stripe.timestamp(),
          optional(:payment_intent_data) => payment_intent_data,
          optional(:payment_method_options) => payment_method_options(),
          optional(:setup_intent_data) => setup_intent_data(),
          optional(:billing_address_collection) => billing_address_collection(),
          optional(:shipping_address_collection) => shipping_address_collection(),
          optional(:submit_type) => submit_type(),
          optional(:subscription_data) => subscription_data,
          optional(:tax_id_collection) => tax_id_collection()
        }

  @typedoc """
  One of `"payment"`, `"setup"`, or `"subscription"`.
  """
  @type mode :: String.t()

  @typedoc """
  One of `"paid"`, `"unpaid"`, or `"no_payment_required"`.
  """
  @type payment_status :: String.t()

  @type phone_number_collection :: %{
          :enabled => boolean()
        }

  @type shipping_option :: %{
          :shipping_amount => non_neg_integer(),
          :shipping_rate => String.t()
        }

  @typedoc """
  One of `"open"`, `"complete"`, or `"expired"`.
  """
  @type status :: String.t()

  @type t :: %__MODULE__{
          id: Stripe.id(),
          object: String.t(),
          after_expiration: expiration() | nil,
          allow_promotion_codes: boolean() | nil,
          amount_subtotal: integer() | nil,
          amount_total: integer() | nil,
          automatic_tax: automatic_tax(),
          billing_address_collection: String.t(),
          cancel_url: boolean(),
          client_reference_id: String.t(),
          consent: consent() | nil,
          consent_collection: consent_collection() | nil,
          currency: String.t(),
          customer: Stripe.id() | Stripe.Customer.t() | nil,
          customer_creation: customer_creation() | nil,
          customer_details: customer_details() | nil,
          customer_email: String.t(),
          line_items: list(line_item),
          expires_at: Stripe.timestamp() | nil,
          livemode: boolean(),
          locale: boolean(),
          metadata: Stripe.Types.metadata(),
          mode: mode(),
          payment_intent: Stripe.id() | Stripe.PaymentIntent.t() | nil,
          payment_link: String.t() | nil,
          payment_method_options: payment_method_options() | nil,
          payment_method_types: list(String.t()),
          payment_status: payment_status(),
          phone_number_collection: phone_number_collection() | nil,
          shipping_options: list(shipping_option()) | nil,
          shipping_rate: String.t() | nil,
          status: status() | nil,
          recovered_from: Stripe.id() | nil,
          setup_intent: Stripe.id() | Stripe.SetupIntent.t() | nil,
          shipping: %{
            address: Stripe.Types.shipping(),
            name: String.t()
          },
          shipping_address_collection: shipping_address_collection(),
          submit_type: submit_type() | nil,
          subscription: Stripe.id() | Stripe.Subscription.t() | nil,
          success_url: String.t(),
          tax_id_collection: tax_id_collection() | nil,
          total_details: total_details() | nil,
          url: String.t()
        }

  defstruct [
    :id,
    :object,
    :after_expiration,
    :allow_promotion_codes,
    :amount_subtotal,
    :amount_total,
    :automatic_tax,
    :billing_address_collection,
    :cancel_url,
    :client_reference_id,
    :consent,
    :consent_collection,
    :currency,
    :customer,
    :customer_creation,
    :customer_details,
    :customer_email,
    :line_items,
    :expires_at,
    :livemode,
    :locale,
    :metadata,
    :mode,
    :payment_intent,
    :payment_link,
    :payment_method_options,
    :payment_method_types,
    :payment_status,
    :phone_number_collection,
    :recovered_from,
    :setup_intent,
    :shipping,
    :shipping_address_collection,
    :shipping_options,
    :shipping_rate,
    :status,
    :submit_type,
    :subscription,
    :success_url,
    :tax_id_collection,
    :total_details,
    :url
  ]

  @plural_endpoint "checkout/sessions"

  @spec create(create_params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
  def create(params, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint)
    |> put_params(params)
    |> put_method(:post)
    |> make_request()
  end

  @doc """
  Retrieve a session.
  """
  @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 """
  Invalidates a session
  """
  @spec expire(Stripe.id() | t) :: {:ok, t} | {:error, Stripe.Error.t()}
  def expire(id, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/expire")
    |> put_method(:post)
    |> make_request()
  end

  @doc """
  List all sessions
  """
  @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()}
        when params: %{
               optional(:subscription) => Stripe.id() | Stripe.Subscription.t(),
               optional(:payment_intent) => Stripe.id() | Stripe.PaymentIntent.t(),
               optional(:limit) => 1..100,
               optional(:ending_before) => t | Stripe.id(),
               optional(:starting_after) => 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([:payment_intent, :customer, :ending_before, :starting_after])
    |> make_request()
  end

  defdelegate list_line_items(id, opts \\ []), to: Stripe.Checkout.Session.LineItems, as: :list
end