Current section

Files

Jump to
revenuecat lib revenuecat.ex
Raw

lib/revenuecat.ex

defmodule RevenueCat do
@moduledoc """
Minimal RevenueCat client.
Exposes `fetch_subscriber/1` as the sole network entrypoint.
"""
@type entitlement_map :: map()
@doc """
Return the entitlements map from a subscriber.
Returns an empty map for non-subscriber inputs.
"""
@spec entitlements(RevenueCat.Subscriber.t()) :: map()
def entitlements(%RevenueCat.Subscriber{} = subscriber) do
subscriber.entitlements || %{}
end
def entitlements(_), do: %{}
@doc """
Return the subscriptions map from a subscriber.
Returns an empty map for non-subscriber inputs.
"""
@spec subscriptions(RevenueCat.Subscriber.t()) :: map()
def subscriptions(%RevenueCat.Subscriber{} = subscriber) do
subscriber.subscriptions || %{}
end
def subscriptions(_), do: %{}
@doc """
Return true if the subscriber has the given entitlement id.
"""
@spec has_entitlement?(RevenueCat.Subscriber.t(), String.t() | atom()) :: boolean()
def has_entitlement?(%RevenueCat.Subscriber{} = subscriber, entitlement_id)
when is_binary(entitlement_id) and entitlement_id != "" do
Map.has_key?(subscriber.entitlements || %{}, entitlement_id)
end
def has_entitlement?(%RevenueCat.Subscriber{} = subscriber, entitlement_id)
when is_atom(entitlement_id) do
has_entitlement?(subscriber, Atom.to_string(entitlement_id))
end
def has_entitlement?(_, _), do: false
@doc """
Fetch a single entitlement struct by id.
"""
@spec entitlement(RevenueCat.Subscriber.t() | map(), String.t() | atom()) ::
RevenueCat.Subscriber.Entitlement.t() | nil
def entitlement(%RevenueCat.Subscriber{} = subscriber, entitlement_id) do
entitlement(subscriber.entitlements || %{}, entitlement_id)
end
def entitlement(%{} = entitlements, entitlement_id) when is_atom(entitlement_id) do
entitlement(entitlements, Atom.to_string(entitlement_id))
end
def entitlement(%{} = entitlements, entitlement_id)
when is_binary(entitlement_id) and entitlement_id != "" do
Map.get(entitlements, entitlement_id)
end
def entitlement(_, _), do: nil
@doc """
Fetch a single subscriber attribute struct by name.
"""
@spec attribute(RevenueCat.Subscriber.t(), String.t() | atom()) ::
RevenueCat.Subscriber.SubscriberAttribute.t() | nil
def attribute(%RevenueCat.Subscriber{} = subscriber, name) do
attribute(subscriber.attributes || %{}, name)
end
def attribute(%{} = attributes, name) when is_atom(name) do
attribute(attributes, Atom.to_string(name))
end
def attribute(%{} = attributes, name) when is_binary(name) and name != "" do
Map.get(attributes, name)
end
def attribute(_, _), do: nil
@doc """
Fetch a single subscription struct by id.
"""
@spec subscription(RevenueCat.Subscriber.t(), String.t() | atom()) ::
RevenueCat.Subscriber.Subscription.t() | nil
def subscription(%RevenueCat.Subscriber{} = subscriber, subscription_id) do
subscription(subscriber.subscriptions || %{}, subscription_id)
end
def subscription(%{} = subscriptions, subscription_id) when is_atom(subscription_id) do
subscription(subscriptions, Atom.to_string(subscription_id))
end
def subscription(%{} = subscriptions, subscription_id)
when is_binary(subscription_id) and subscription_id != "" do
Map.get(subscriptions, subscription_id)
end
def subscription(_, _), do: nil
@doc """
Return true if the subscriber has the given subscription id.
"""
@spec has_subscription?(RevenueCat.Subscriber.t(), String.t() | atom()) :: boolean()
def has_subscription?(%RevenueCat.Subscriber{} = subscriber, subscription_id)
when is_binary(subscription_id) and subscription_id != "" do
Map.has_key?(subscriber.subscriptions || %{}, subscription_id)
end
def has_subscription?(%RevenueCat.Subscriber{} = subscriber, subscription_id)
when is_atom(subscription_id) do
has_subscription?(subscriber, Atom.to_string(subscription_id))
end
def has_subscription?(_, _), do: false
@doc """
Fetch a subscriber from RevenueCat by app user id.
"""
@spec fetch_subscriber(String.t()) ::
{:ok, RevenueCat.Subscriber.t()} | {:error, term()}
def fetch_subscriber(app_user_id)
when is_binary(app_user_id) and byte_size(app_user_id) > 0 do
with {:ok, body} <-
RevenueCat.Client.do_request(:get, "/v1/subscribers/" <> URI.encode(app_user_id)),
{:ok, decoded} <- Jason.decode(body),
{:ok, subscriber} <- RevenueCat.Subscriber.from_response(decoded) do
{:ok, subscriber}
else
{:error, reason} -> {:error, reason}
end
end
def fetch_subscriber(_), do: {:error, :invalid_request}
@doc """
Update customer attributes and return the updated subscriber.
"""
@spec update_customer_attributes(String.t(), map()) ::
{:ok, RevenueCat.Subscriber.t()} | {:error, term()}
def update_customer_attributes(app_user_id, attributes)
when is_binary(app_user_id) and byte_size(app_user_id) > 0 and is_map(attributes) do
path = "/v1/subscribers/" <> URI.encode(app_user_id) <> "/attributes"
payload = %{"attributes" => normalize_attributes(attributes)}
with {:ok, body} <- RevenueCat.Client.do_request(:post, path, json: payload),
{:ok, decoded} <- Jason.decode(body) do
case subscriber_from_update_response(decoded) do
{:ok, subscriber} -> {:ok, subscriber}
{:error, _} -> fetch_subscriber(app_user_id)
end
else
{:error, %Jason.DecodeError{}} -> fetch_subscriber(app_user_id)
{:error, reason} -> {:error, reason}
end
end
def update_customer_attributes(_, _), do: {:error, :invalid_request}
defp subscriber_from_update_response(%{"value" => value}) when is_map(value) do
RevenueCat.Subscriber.from_response(value)
end
defp subscriber_from_update_response(value) when is_map(value) do
RevenueCat.Subscriber.from_response(value)
end
defp subscriber_from_update_response(_), do: {:error, :missing_subscriber}
defp normalize_attributes(attributes) when is_map(attributes) do
Map.new(attributes, fn {key, value} -> {key, normalize_attribute_value(value)} end)
end
defp normalize_attribute_value(%{"value" => _} = value), do: value
defp normalize_attribute_value(%{value: _} = value) do
Map.new(value, fn {k, v} -> {to_string(k), v} end)
end
defp normalize_attribute_value(value) when is_binary(value) or is_nil(value) do
%{"value" => value}
end
defp normalize_attribute_value(value) do
%{"value" => to_string(value)}
end
end