HawkEx.Billing (hawk_ex v0.1.0)

Copy Markdown View Source

Public API for plan and subscription management.

HawkEx.Billing owns the lifecycle of account subscriptions: creating a subscription, finding the current subscription or plan, canceling access, and changing plans. Functions accept either an account struct with an :id field or a raw account id string.

Subscription changes emit events through HawkEx.Events when PubSub is configured. Without PubSub, the billing operations still complete and event emission is a no-op.

Examples

HawkEx.Billing.subscribe(account, :pro)
HawkEx.Billing.current_subscription(account)
HawkEx.Billing.cancel(account)
HawkEx.Billing.change_plan(account, :enterprise)

Error tuples

The context returns tagged error tuples for expected business failures:

  • {:error, :plan_not_found} - no active plan matches the requested name.
  • {:error, :active_subscription_exists} - the account already has a trialing or active subscription.
  • {:error, :no_active_subscription} - cancel or change-plan was requested for an account with no active subscription.

Summary

Functions

Cancels the account's active subscription immediately.

Changes an account's current plan in place.

Returns the current plan for an account.

Returns the current trialing or active subscription for an account.

Returns a single subscription by id with its plan preloaded.

Returns a paginated list of trialing and active subscriptions.

Searches active subscriptions by account id or plan name.

Creates a subscription for an account on the given plan.

Functions

cancel(account)

Cancels the account's active subscription immediately.

The subscription record is kept with status "canceled" and canceled_at set to the current UTC time. Subscriptions are never deleted.

Returns {:ok, subscription} on success or {:error, :no_active_subscription} when there is no active subscription.

change_plan(account, new_plan_name)

Changes an account's current plan in place.

This updates plan_id on the existing active subscription. It does not cancel and recreate the subscription, so billing period history remains on a single record in the current release.

Returns {:ok, subscription} on success, {:error, :plan_not_found} when the target plan is not active, or {:error, :no_active_subscription} when there is no active subscription to change.

current_plan(account)

Returns the current plan for an account.

Returns nil when the account has no active subscription.

current_subscription(account)

Returns the current trialing or active subscription for an account.

The returned subscription has its plan preloaded. Returns nil when the account has no active subscription.

get_subscription(id)

Returns a single subscription by id with its plan preloaded.

Returns nil if no subscription exists for the id.

recent_subscriptions(opts \\ [])

Returns a paginated list of trialing and active subscriptions.

Entries are ordered newest first and preload :plan.

Options

  • :page - 1-indexed page number. Defaults to 1.

  • :per_page - rows per page. Defaults to 50.

  • :search - optional search term matching account_id.

    Matches against the UUID cast to text, since exact-substring search on a raw UUID is rarely practical for a human typing into a search box.

Returns %{entries:, page:, per_page:, total_count:, total_pages:}.

Example

HawkEx.Billing.recent_subscriptions(page: 1, per_page: 50)
# => %{entries: [...], page: 1, per_page: 50, total_count: 25, total_pages: 2}

search_subscriptions(query, opts \\ [])

Searches active subscriptions by account id or plan name.

Accepts :limit in opts, defaulting to 5. Results are ordered newest first and preload :plan.

subscribe(account, plan_name)

Creates a subscription for an account on the given plan.

plan_name may be an atom or string and is matched against active HawkEx.Billing.Plan.name values. If the plan has trial_days > 0, the subscription starts in "trialing" status. Otherwise it starts as "active".

Returns {:ok, subscription} on success.

Returns:

  • {:error, :plan_not_found}
  • {:error, :active_subscription_exists}
  • {:error, changeset} if the repo rejects the insert.