Packages
phoenix_kit
1.7.21
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/billing/schemas/subscription_plan.ex
defmodule PhoenixKit.Modules.Billing.SubscriptionPlan do
@moduledoc """
Schema for subscription plans (pricing tiers).
Subscription plans define the pricing, billing interval, and features
available at each tier. Plans are managed internally and used to
create subscriptions.
## Fields
- `name` - Display name (e.g., "Basic", "Pro", "Enterprise")
- `code` - Unique identifier code (e.g., "basic", "pro")
- `description` - Marketing description
- `price` - Price per billing period (Decimal)
- `currency` - Three-letter currency code (default: "EUR")
- `interval` - Billing interval: "day", "week", "month", "year"
- `interval_count` - Number of intervals (e.g., 3 months)
- `trial_days` - Free trial period in days (default: 0)
- `features` - JSON map of features included in this plan
- `active` - Whether this plan is available for new subscriptions
- `sort_order` - Display order in plan listings
## Examples
%SubscriptionPlan{
name: "Professional",
code: "pro",
price: Decimal.new("29.99"),
currency: "EUR",
interval: "month",
interval_count: 1,
trial_days: 14,
features: %{"api_calls" => 10000, "storage_gb" => 50},
active: true
}
"""
use Ecto.Schema
import Ecto.Changeset
alias PhoenixKit.RepoHelper
@intervals ~w(day week month year)
schema "phoenix_kit_subscription_plans" do
field :uuid, Ecto.UUID
field :name, :string
field :slug, :string
field :description, :string
field :price, :decimal
field :currency, :string, default: "EUR"
field :interval, :string, default: "month"
field :interval_count, :integer, default: 1
field :trial_days, :integer, default: 0
field :features, {:array, :string}, default: []
field :active, :boolean, default: true
field :sort_order, :integer, default: 0
field :metadata, :map, default: %{}
timestamps(type: :utc_datetime)
end
@doc """
Creates a changeset for a subscription plan.
## Required fields
- `name` - Plan display name
- `slug` - Unique plan identifier (URL-friendly)
- `price` - Price per period
## Optional fields
- `description`, `currency`, `interval`, `interval_count`
- `trial_days`, `features`, `active`, `sort_order`, `metadata`
"""
def changeset(plan, attrs) do
plan
|> cast(attrs, [
:name,
:slug,
:description,
:price,
:currency,
:interval,
:interval_count,
:trial_days,
:features,
:active,
:sort_order,
:metadata
])
|> validate_required([:name, :slug, :price])
|> validate_inclusion(:interval, @intervals)
|> validate_number(:price, greater_than_or_equal_to: 0)
|> validate_number(:interval_count, greater_than: 0)
|> validate_number(:trial_days, greater_than_or_equal_to: 0)
|> validate_length(:slug, min: 1, max: 50)
|> validate_length(:currency, is: 3)
|> unique_constraint(:slug)
|> maybe_generate_uuid()
end
defp maybe_generate_uuid(changeset) do
case get_field(changeset, :uuid) do
nil -> put_change(changeset, :uuid, UUIDv7.generate())
_ -> changeset
end
end
@doc """
Returns the billing period in days for this plan.
"""
def billing_period_days(%__MODULE__{interval: interval, interval_count: count}) do
base_days =
case interval do
"day" -> 1
"week" -> 7
"month" -> 30
"year" -> 365
end
base_days * count
end
@doc """
Calculates the next billing date from a given start date.
"""
def next_billing_date(%__MODULE__{interval: interval, interval_count: count}, from_date) do
case interval do
"day" ->
Date.add(from_date, count)
"week" ->
Date.add(from_date, count * 7)
"month" ->
# Use Elixir's Date.shift for proper month handling
Date.shift(from_date, month: count)
"year" ->
Date.shift(from_date, year: count)
end
end
@doc """
Returns the formatted price string with currency.
"""
def formatted_price(%__MODULE__{price: price, currency: currency}) do
"#{Decimal.round(price, 2)} #{currency}"
end
@doc """
Returns the billing interval description (e.g., "monthly", "every 3 months").
"""
def interval_description(%__MODULE__{interval: interval, interval_count: 1}) do
case interval do
"day" -> "daily"
"week" -> "weekly"
"month" -> "monthly"
"year" -> "yearly"
end
end
def interval_description(%__MODULE__{interval: interval, interval_count: count}) do
"every #{count} #{interval}s"
end
@doc """
Lists all active subscription plans ordered by sort_order.
"""
def list_active do
import Ecto.Query
from(p in __MODULE__,
where: p.active == true,
order_by: [asc: p.sort_order, asc: p.name]
)
|> RepoHelper.repo().all()
end
@doc """
Gets a plan by its code.
"""
def get_by_code(code) do
RepoHelper.repo().get_by(__MODULE__, code: code)
end
end