Phase 3: deployment model + subscriptions
cloud_deployments — the billable unit (one app instance). A tenant has 1..N deployments; cloud_resources.deployment_id ties resources to one. Fields: tenant_id, slug (unique per tenant), display_name, region, state, llm_mode, billing_action_suspended (operator override), template_code/version (nullable — formal templates land in phase 4). Lifecycle state machine in ArcadiaCloud.Deployments — states trial / active / past_due / paused / suspended / cancelled / archived. Every transition is validated against an explicit @transitions map and recorded in cloud_deployment_events. create_deployment defaults to `active` (trial is wired but no flow enters it yet). subscriptions — one per deployment, binds it to a plan_version. status active/paused/cancelled, current period dates, trial_ends_at. subscription_addons — addons attached to a subscription with price + qty SNAPSHOTTED at attach time, so a later catalog price change can't retroactively reprice an existing subscriber. ArcadiaCloud.Subscriptions context: create_subscription (period defaults to current calendar month), attach_addon (snapshots from the live Addon), change_plan_version (migrate to a new version — price changes / up-down grades), get_subscription_for_deployment. API (platform_admin sees all tenants; others scoped to own tenant_id): - GET/POST /api/v1/deployments - GET /api/v1/deployments/:id (with subscription + events) - POST /api/v1/deployments/:id/transition - POST /api/v1/deployments/:id/subscribe (plan_code + optional addons) Smoke verified: created a deployment, transitioned active->paused (events logged with actor), rejected an invalid paused->archived transition (422), subscribed to Studio with the storage_50gb addon — addon price snapshotted at 750c/qty 50; show returns deployment + subscription + event history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
33
lib/arcadia_cloud/billing/subscription.ex
Normal file
33
lib/arcadia_cloud/billing/subscription.ex
Normal file
@@ -0,0 +1,33 @@
|
||||
defmodule ArcadiaCloud.Billing.Subscription do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
|
||||
@statuses ~w(active paused cancelled)
|
||||
|
||||
schema "subscriptions" do
|
||||
field :status, :string, default: "active"
|
||||
field :current_period_start, :date
|
||||
field :current_period_end, :date
|
||||
field :trial_ends_at, :date
|
||||
|
||||
belongs_to :deployment, ArcadiaCloud.Deployments.CloudDeployment
|
||||
belongs_to :plan_version, ArcadiaCloud.Catalog.PlanVersion
|
||||
has_many :addons, ArcadiaCloud.Billing.SubscriptionAddon
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@required ~w(deployment_id plan_version_id current_period_start current_period_end)a
|
||||
@optional ~w(status trial_ends_at)a
|
||||
|
||||
def changeset(sub, attrs) do
|
||||
sub
|
||||
|> cast(attrs, @required ++ @optional)
|
||||
|> validate_required(@required)
|
||||
|> validate_inclusion(:status, @statuses)
|
||||
|> unique_constraint(:deployment_id)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user