Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions apps/billing/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,3 @@
class BillingConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.billing"

def ready(self):
# Importing tasks connects the task_failure signal handler defined there.
from apps.billing import tasks # noqa: F401
1 change: 0 additions & 1 deletion apps/billing/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
class PlanCodeType:
# Plan code used as the default when provisioning a new organization.
FREE = "free"
PRO = "pro"


class CurrencyType(models.TextChoices):
Expand Down
2 changes: 1 addition & 1 deletion apps/billing/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
("organization", "0004_alter_organization_template"),
("organization", "0001_initial"),
]

operations = [
Expand Down
80 changes: 4 additions & 76 deletions apps/billing/migrations/0002_seed_default_plans_and_features.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
"""Seed default Free and Pro plans with their feature definitions."""
"""Seed the default Community plan with its feature definitions."""

from django.db import migrations

# Plan codes
FREE_PLAN_CODE = "free"
PRO_PLAN_CODE = "pro"

PLANS = [
{
"code": FREE_PLAN_CODE,
"name": "Free",
"description": "First 10 devices free.\n1 week Data Retention",
},
{
"code": PRO_PLAN_CODE,
"name": "Pro",
"description": "Up to 100 devices\n6 months Data Retention",
"name": "Community",
"description": "Default open-source workspace limits.",
},
]

Expand All @@ -27,35 +21,15 @@
"discount": 0,
"billing_cycle": "monthly",
},
{
"plan_code": PRO_PLAN_CODE,
"price": 99,
"currency": "USD",
"discount": 10,
"billing_cycle": "monthly",
},
]

# Feature catalog (9 features). value_type drives the PlanFeature shape:
# Feature catalog. value_type drives the PlanFeature shape:
# limit -> limit_value is the numeric cap (null = unlimited)
# quota -> limit_value is the quota amount
# boolean -> limit_value is null; use `enabled`
FEATURES = [
{"code": "device.max_count", "name": "Device(s)", "value_type": "limit"},
{"code": "space.max_count", "name": "Space(s)", "value_type": "limit"},
{"code": "dashboard.max_count", "name": "Dashboard(s)", "value_type": "limit"},
{
"code": "dashboard.basic_widgets",
"name": "Basic widgets",
"value_type": "boolean",
},
{
"code": "dashboard.custom_charts",
"name": "Custom charts & maps",
"value_type": "boolean",
},
{"code": "map_view.2d", "name": "2D map view", "value_type": "boolean"},
{"code": "map_view.3d", "name": "3D map view", "value_type": "boolean"},
{
"code": "whitelabel.enabled",
"name": "White-label branding",
Expand All @@ -66,28 +40,6 @@
"name": "Automation",
"value_type": "limit",
},
{
"code": "data_retention.days",
"name": "Data retention (days)",
"value_type": "quota",
},
{
"code": "support.onboarding_video",
"name": "Onboarding video",
"value_type": "boolean",
},
{"code": "support.email", "name": "Email support", "value_type": "boolean"},
{
"code": "support.email_community",
"name": "Email, community support",
"value_type": "boolean",
},
{"code": "support.priority", "name": "Priority support", "value_type": "boolean"},
{
"code": "support.fully_maintenance",
"name": "Fully maintenance",
"value_type": "boolean",
},
]

# Per-plan feature values. Keys are feature codes;
Expand All @@ -96,35 +48,11 @@
"space.max_count": {"enabled": True, "limit_value": 1},
"dashboard.max_count": {"enabled": True, "limit_value": 1},
"automation.max_count": {"enabled": True, "limit_value": 0},
"dashboard.basic_widgets": {"enabled": True, "limit_value": None},
"map_view.2d": {"enabled": True, "limit_value": None},
"map_view.3d": {"enabled": True, "limit_value": None},
"data_retention.days": {"enabled": True, "limit_value": 7},
"support.onboarding_video": {"enabled": True, "limit_value": None},
"support.email": {"enabled": True, "limit_value": None},
}

PRO_FEATURES = {
"device.max_count": {"enabled": True, "limit_value": 100},
"space.max_count": {"enabled": True, "limit_value": None},
"dashboard.max_count": {"enabled": True, "limit_value": None},
"dashboard.basic_widgets": {"enabled": True, "limit_value": None},
"dashboard.custom_charts": {"enabled": True, "limit_value": None},
"map_view.2d": {"enabled": True, "limit_value": None},
"map_view.3d": {"enabled": True, "limit_value": None},
"whitelabel.enabled": {"enabled": True, "limit_value": None},
"data_retention.days": {"enabled": True, "limit_value": 180},
"support.onboarding_video": {"enabled": True, "limit_value": None},
"support.email": {"enabled": True, "limit_value": None},
"support.email_community": {"enabled": True, "limit_value": None},
"support.priority": {"enabled": True, "limit_value": None},
"support.fully_maintenance": {"enabled": True, "limit_value": None},
"automation.max_count": {"enabled": True, "limit_value": 5},
}

PLAN_FEATURE_VALUES = {
FREE_PLAN_CODE: FREE_FEATURES,
PRO_PLAN_CODE: PRO_FEATURES,
}

FEATURE_CODES = [f["code"] for f in FEATURES]
Expand Down
92 changes: 0 additions & 92 deletions apps/billing/serializers.py
Original file line number Diff line number Diff line change
@@ -1,98 +1,6 @@
from common.apps.billing.constants import FeatureUsageScope
from rest_framework import serializers

from apps.billing.models import Feature, Plan, PlanFeature, PlanItem


class FeatureSerializer(serializers.ModelSerializer):
class Meta:
model = Feature
fields = [
"id",
"code",
"name",
"description",
"value_type",
]
extra_kwargs = {
"id": {"read_only": True},
}


class PlanFeatureSerializer(serializers.ModelSerializer):
feature = FeatureSerializer(read_only=True)

class Meta:
model = PlanFeature
fields = [
"id",
"feature",
"enabled",
"limit_value",
"metadata",
]
extra_kwargs = {
"id": {"read_only": True},
}


class PlanItemSerializer(serializers.ModelSerializer):
class Meta:
model = PlanItem
fields = [
"id",
"price",
"icon",
"currency",
"discount",
"billing_cycle",
"is_active",
"created_at",
"updated_at",
]
extra_kwargs = {"id": {"read_only": True}}


class PlanSerializer(serializers.ModelSerializer):
plan_items = PlanItemSerializer(many=True, read_only=True)
is_current_plan = serializers.BooleanField(read_only=True, default=True)

class Meta:
model = Plan
fields = [
"id",
"name",
"code",
"description",
"plan_items",
"is_current_plan",
"created_at",
"updated_at",
]
extra_kwargs = {"id": {"read_only": True}}


class PlanWithFeaturesSerializer(PlanSerializer):
"""Plan with nested feature definitions — for plan comparison views."""

def to_representation(self, instance):
data = super().to_representation(instance)
plan_features = instance.plan_features.all()
features = []
support = []
for plan_feature in plan_features:
if plan_feature.feature.code.startswith("support."):
support.append(plan_feature)
else:
features.append(plan_feature)

data["features"] = PlanFeatureSerializer(features, many=True).data
data["support"] = PlanFeatureSerializer(support, many=True).data
return data

class Meta(PlanSerializer.Meta):
fields = list(PlanSerializer.Meta.fields) + ["plan_features"]


class ReserveQuotaSerializer(serializers.Serializer):
feature = serializers.CharField()
Expand Down
3 changes: 1 addition & 2 deletions apps/billing/services/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from apps.billing.services.chargebee_webhook import process_chargebee_event
from apps.billing.services.subscription import create_default_subscription

__all__ = ["create_default_subscription", "process_chargebee_event"]
__all__ = ["create_default_subscription"]
Loading
Loading