lib/stripe/subscriptions/price.ex

defmodule Stripe.Price do
  @moduledoc """
  Work with Stripe price objects.

  The Prices API adds more flexibility to how you charge customers.

  It also replaces the Plans API, so Stripe recommends migrating your existing
  integration to work with prices.

  To migrate, you need to identify how you use plans, products, and payment
  flows and then update these parts of your integration to use the Prices API.

  Migrating to Prices guide: https://stripe.com/docs/billing/migration/migrating-prices

  You can:

  - Create a price
  - Retrieve a price
  - Update a price
  - List all prices
  - Search prices

  Stripe API reference: https://stripe.com/docs/api/prices

  Example:

  ```
  {
    "id": "plan_HJ8MK9HTYgniMM",
    "object": "price",
    "active": true,
    "billing_scheme": "per_unit",
    "created": 1589897226,
    "currency": "usd",
    "livemode": false,
    "lookup_key": null,
    "metadata": {},
    "nickname": null,
    "product": "prod_HJ8MOtuM1vD2jd",
    "recurring": {
      "aggregate_usage": null,
      "interval": "month",
      "interval_count": 1,
      "trial_period_days": null,
      "usage_type": "licensed"
    },
    "tax_behavior": "unspecified",
    "tiers": null,
    "tiers_mode": null,
    "transform_lookup_key": false,
    "transform_quantity": null,
    "type": "recurring",
    "unit_amount": 999,
    "unit_amount_decimal": "999"
  }
  ```
  """

  use Stripe.Entity
  import Stripe.Request

  @type recurring :: %{
          optional(:aggregate_usage) => String.t(),
          optional(:interval) => String.t(),
          optional(:interval_count) => pos_integer,
          optional(:trial_period_days) => pos_integer,
          optional(:usage_type) => String.t()
        }

  @type price_tier :: %{
          flat_amount: integer,
          flat_amount_decimal: String.t(),
          unit_amount: integer,
          unit_amount_decimal: String.t(),
          up_to: integer
        }

  @type transform_quantity :: %{
          divide_by: pos_integer,
          round: String.t()
        }

  @type product_data :: %{
          :name => String.t(),
          optional(:active) => boolean,
          optional(:metadata) => map,
          optional(:statement_descriptor) => String.t(),
          optional(:tax_code) => String.t(),
          optional(:unit_label) => String.t()
        }

  @type t :: %__MODULE__{
          id: Stripe.id(),
          object: String.t(),
          active: boolean,
          billing_scheme: String.t(),
          created: Stripe.timestamp(),
          currency: String.t(),
          livemode: boolean,
          lookup_key: String.t(),
          metadata: Stripe.Types.metadata(),
          nickname: String.t(),
          product: Stripe.id() | Stripe.Product.t(),
          recurring: recurring(),
          tax_behavior: String.t(),
          tiers: [price_tier()],
          tiers_mode: String.t(),
          transform_lookup_key: boolean(),
          transform_quantity: transform_quantity(),
          type: String.t(),
          unit_amount: pos_integer,
          unit_amount_decimal: String.t()
        }

  defstruct [
    :id,
    :object,
    :active,
    :billing_scheme,
    :created,
    :currency,
    :livemode,
    :lookup_key,
    :metadata,
    :nickname,
    :product,
    :recurring,
    :tax_behavior,
    :tiers,
    :tiers_mode,
    :transform_lookup_key,
    :transform_quantity,
    :type,
    :unit_amount,
    :unit_amount_decimal
  ]

  @plural_endpoint "prices"

  @doc """
  Create a price.
  """
  @spec create(params, Stripe.options()) ::
          {:ok, t} | {:error, Stripe.Error.t()}
        when params:
               %{
                 :currency => String.t(),
                 optional(:unit_amount) => pos_integer,
                 optional(:active) => boolean,
                 optional(:metadata) => Stripe.Types.metadata(),
                 optional(:nickname) => String.t(),
                 optional(:product) => Stripe.id() | Stripe.Product.t(),
                 optional(:product_data) => product_data,
                 optional(:recurring) => recurring(),
                 optional(:tax_behavior) => String.t(),
                 optional(:tiers) => [price_tier()],
                 optional(:tiers_mode) => String.t(),
                 optional(:billing_scheme) => String.t(),
                 optional(:lookup_key) => String.t(),
                 optional(:transfer_lookup_key) => boolean,
                 optional(:transform_quantity) => transform_quantity(),
                 optional(:unit_amount_decimal) => String.t()
               }
               | %{}
  def create(params, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint)
    |> put_params(params)
    |> put_method(:post)
    |> cast_to_id([:product])
    |> make_request()
  end

  @doc """
  Retrieve a price.
  """
  @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 price.

  Takes the `id` and a map of changes.
  """
  @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
        when params:
               %{
                 optional(:active) => boolean,
                 optional(:metadata) => Stripe.Types.metadata(),
                 optional(:nickname) => String.t(),
                 optional(:recurring) => recurring(),
                 optional(:lookup_key) => String.t(),
                 optional(:transfer_lookup_key) => boolean
               }
               | %{}
  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 """
  List all prices.
  """
  @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()}
        when params:
               %{
                 optional(:active) => boolean,
                 optional(:currency) => String.t(),
                 optional(:product) => Stripe.Product.t() | Stripe.id(),
                 optional(:type) => String.t(),
                 optional(:created) => Stripe.timestamp(),
                 optional(:ending_before) => t | Stripe.id(),
                 optional(:limit) => 1..100,
                 optional(:lookup_keys) => list(String.t()),
                 optional(:recurring) => recurring() | nil,
                 optional(:starting_after) => t | Stripe.id()
               }
               | %{}
  def list(params \\ %{}, opts \\ []) do
    new_request(opts)
    |> put_endpoint(@plural_endpoint)
    |> put_method(:get)
    |> put_params(params)
    |> cast_to_id([:product, :ending_before, :starting_after])
    |> make_request()
  end

  @doc """
  Search prices
  """
  @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