diff --git a/ee/api/test/test_billing.py b/ee/api/test/test_billing.py
index d4586149f16c3..2b4d38dd85bd8 100644
--- a/ee/api/test/test_billing.py
+++ b/ee/api/test/test_billing.py
@@ -375,19 +375,11 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma
"unit_amount_usd": "0.00",
"up_to": 1000000,
"current_amount_usd": "0.00",
- "current_usage": 0,
- "flat_amount_usd": "0",
- "projected_amount_usd": "None",
- "projected_usage": None,
},
{
"unit_amount_usd": "0.00045",
"up_to": 2000000,
"current_amount_usd": "0.00",
- "current_usage": 0,
- "flat_amount_usd": "0",
- "projected_amount_usd": "None",
- "projected_usage": None,
},
],
"tiered": True,
@@ -418,19 +410,11 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma
"tiers": [
{
"current_amount_usd": "0.00",
- "current_usage": 0,
- "flat_amount_usd": "0",
- "projected_amount_usd": "None",
- "projected_usage": None,
"unit_amount_usd": "0.00",
"up_to": 1000000,
},
{
"current_amount_usd": "0.00",
- "current_usage": 0,
- "flat_amount_usd": "0",
- "projected_amount_usd": "None",
- "projected_usage": None,
"unit_amount_usd": "0.0000135",
"up_to": 2000000,
},
@@ -512,7 +496,7 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma
],
"current_usage": 0,
"percentage_usage": 0,
- "current_amount_usd": "0.00",
+ "current_amount_usd": 0.0,
"has_exceeded_limit": False,
"projected_amount_usd": 0.0,
"projected_amount": 0,
@@ -525,7 +509,7 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma
"usage_key": "events",
"addons": [
{
- "current_amount_usd": "0.00",
+ "current_amount_usd": 0.0,
"current_usage": 0,
"description": "Test Addon",
"free_allocation": 10000,
@@ -758,217 +742,15 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma
res_json = res.json()
# Should update product usage to reflect today's usage
assert res_json["products"][0]["current_usage"] == 1101000
- assert res_json["products"][0]["current_amount_usd"] == "45.45"
- assert res_json["products"][0]["tiers"][0]["current_usage"] == 1000000
- assert res_json["products"][0]["tiers"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["tiers"][1]["current_usage"] == 101000
- assert res_json["products"][0]["tiers"][1]["current_amount_usd"] == "45.45"
-
- assert res_json["products"][0]["addons"][0]["current_usage"] == 1101000
- assert res_json["products"][0]["addons"][0]["current_amount_usd"] == "1.36"
- assert res_json["products"][0]["addons"][0]["tiers"][0]["current_usage"] == 1000000
- assert res_json["products"][0]["addons"][0]["tiers"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["addons"][0]["tiers"][1]["current_usage"] == 101000
- assert res_json["products"][0]["addons"][0]["tiers"][1]["current_amount_usd"] == "1.36"
-
- # Now test when there is a usage_limit.
- def mock_implementation_with_limit(url: str, headers: Any = None, params: Any = None) -> MagicMock:
- mock = MagicMock()
- mock.status_code = 404
-
- if "api/billing/portal" in url:
- mock.status_code = 200
- mock.json.return_value = {"url": "https://billing.stripe.com/p/session/test_1234"}
- elif "api/billing" in url:
- mock.status_code = 200
- mock.json.return_value = create_billing_response(
- customer=create_billing_customer(has_active_subscription=True),
- )
- mock.json.return_value["customer"]["usage_summary"]["events"]["usage"] = 1000000
- mock.json.return_value["customer"]["usage_summary"]["events"]["limit"] = 1000000
- elif "api/products" in url:
- mock.status_code = 200
- mock.json.return_value = create_billing_products_response()
-
- return mock
-
- mock_request.side_effect = mock_implementation_with_limit
- self.organization.usage = {"events": {"limit": 1000000, "usage": 1000000, "todays_usage": 100}}
- self.organization.save()
-
- res = self.client.get("/api/billing")
- assert res.status_code == 200
- res_json = res.json()
- # Should update product usage to reflect today's usage
- assert res_json["products"][0]["current_usage"] == 1000100
- assert res_json["products"][0]["current_amount_usd"] == "0.04"
- assert res_json["products"][0]["tiers"][0]["current_usage"] == 1000000
- assert res_json["products"][0]["tiers"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["tiers"][1]["current_usage"] == 100
- assert res_json["products"][0]["tiers"][1]["current_amount_usd"] == "0.04"
-
- assert res_json["products"][0]["addons"][0]["current_usage"] == 1000100
- assert res_json["products"][0]["addons"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["addons"][0]["tiers"][0]["current_usage"] == 1000000
- assert res_json["products"][0]["addons"][0]["tiers"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["addons"][0]["tiers"][1]["current_usage"] == 100
- assert res_json["products"][0]["addons"][0]["tiers"][1]["current_amount_usd"] == "0.00"
-
- def mock_implementation_exceeds_limit(url: str, headers: Any = None, params: Any = None) -> MagicMock:
- mock = MagicMock()
- mock.status_code = 404
-
- if "api/billing/portal" in url:
- mock.status_code = 200
- mock.json.return_value = {"url": "https://billing.stripe.com/p/session/test_1234"}
- elif "api/billing" in url:
- mock.status_code = 200
- mock.json.return_value = create_billing_response(
- customer=create_billing_customer(has_active_subscription=True),
- )
- mock.json.return_value["customer"]["usage_summary"]["events"]["usage"] = 1100000
- mock.json.return_value["customer"]["usage_summary"]["events"]["limit"] = 1000000
- elif "api/products" in url:
- mock.status_code = 200
- mock.json.return_value = create_billing_products_response()
-
- return mock
-
- mock_request.side_effect = mock_implementation_exceeds_limit
- self.organization.usage = {"events": {"limit": 1000000, "usage": 1100000, "todays_usage": 1000}}
- self.organization.save()
-
- res = self.client.get("/api/billing")
- assert res.status_code == 200
- res_json = res.json()
- # Should update product usage to reflect today's usage
- assert res_json["products"][0]["current_usage"] == 1101000
- assert res_json["products"][0]["current_amount_usd"] == "45.00"
- assert res_json["products"][0]["tiers"][0]["current_usage"] == 1000000
- assert res_json["products"][0]["tiers"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["tiers"][1]["current_usage"] == 100000
- assert res_json["products"][0]["tiers"][1]["current_amount_usd"] == "45.00"
-
- assert res_json["products"][0]["addons"][0]["current_usage"] == 1101000
- assert res_json["products"][0]["addons"][0]["current_amount_usd"] == "1.35"
- assert res_json["products"][0]["addons"][0]["tiers"][0]["current_usage"] == 1000000
- assert res_json["products"][0]["addons"][0]["tiers"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["addons"][0]["tiers"][1]["current_usage"] == 100000
- assert res_json["products"][0]["addons"][0]["tiers"][1]["current_amount_usd"] == "1.35"
-
- # Test when the customer has no usage. Ensure that the tiered current_usage isn't set to the usage limit.
- def mock_implementation_with_limit_no_usage(url: str, headers: Any = None, params: Any = None) -> MagicMock:
- mock = MagicMock()
- mock.status_code = 404
-
- if "api/billing/portal" in url:
- mock.status_code = 200
- mock.json.return_value = {"url": "https://billing.stripe.com/p/session/test_1234"}
- elif "api/billing" in url:
- mock.status_code = 200
- mock.json.return_value = create_billing_response(
- customer=create_billing_customer(has_active_subscription=True),
- )
- mock.json.return_value["customer"]["usage_summary"]["events"]["usage"] = 0
- mock.json.return_value["customer"]["usage_summary"]["events"]["limit"] = 1000000
- elif "api/products" in url:
- mock.status_code = 200
- mock.json.return_value = create_billing_products_response()
-
- return mock
-
- mock_request.side_effect = mock_implementation_with_limit_no_usage
-
- self.organization.usage = {"events": {"limit": 1000000, "usage": 0, "todays_usage": 0}}
- self.organization.save()
-
- res = self.client.get("/api/billing")
- assert res.status_code == 200
- res_json = res.json()
- # Should update product usage to reflect today's usage
- assert res_json["products"][0]["current_usage"] == 0
assert res_json["products"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["tiers"][0]["current_usage"] == 0
assert res_json["products"][0]["tiers"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["tiers"][1]["current_usage"] == 0
assert res_json["products"][0]["tiers"][1]["current_amount_usd"] == "0.00"
assert res_json["products"][0]["addons"][0]["current_usage"] == 0
assert res_json["products"][0]["addons"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["addons"][0]["tiers"][0]["current_usage"] == 0
assert res_json["products"][0]["addons"][0]["tiers"][0]["current_amount_usd"] == "0.00"
- assert res_json["products"][0]["addons"][0]["tiers"][1]["current_usage"] == 0
assert res_json["products"][0]["addons"][0]["tiers"][1]["current_amount_usd"] == "0.00"
- def mock_implementation_missing_customer(url: str, headers: Any = None, params: Any = None) -> MagicMock:
- mock = MagicMock()
- mock.status_code = 404
-
- if "api/billing/portal" in url:
- mock.status_code = 200
- mock.json.return_value = {"url": "https://billing.stripe.com/p/session/test_1234"}
- elif "api/billing" in url:
- mock.status_code = 200
- mock.json.return_value = create_billing_response(customer=create_missing_billing_customer())
- elif "api/products" in url:
- mock.status_code = 200
- mock.json.return_value = create_billing_products_response()
-
- return mock
-
- mock_request.side_effect = mock_implementation_missing_customer
-
- # Test unsubscribed config
- res = self.client.get("/api/billing")
- self.organization.refresh_from_db()
- assert self.organization.usage == {
- "events": {
- "limit": None,
- "todays_usage": 0,
- "usage": 0,
- },
- "recordings": {
- "limit": None,
- "todays_usage": 0,
- "usage": 0,
- },
- "rows_synced": {
- "limit": None,
- "todays_usage": 0,
- "usage": 0,
- },
- "period": ["2022-10-07T11:12:48", "2022-11-07T11:12:48"],
- }
- assert self.organization.customer_id == "cus_123"
-
- # Now test when there is a tiered product in the response that isn't in the usage dict
- def mock_implementation(url: str, headers: Any = None, params: Any = None) -> MagicMock:
- mock = MagicMock()
- mock.status_code = 404
-
- if "api/billing/portal" in url:
- mock.status_code = 200
- mock.json.return_value = {"url": "https://billing.stripe.com/p/session/test_1234"}
- elif "api/billing" in url:
- mock.status_code = 200
- mock.json.return_value = create_billing_response(
- customer=create_billing_customer(has_active_subscription=True),
- )
- mock.json.return_value["customer"]["products"][0]["usage_key"] = "feature_flag_requests"
- mock.json.return_value["customer"]["usage_summary"]["events"]["usage"] = 1000
- elif "api/products" in url:
- mock.status_code = 200
- mock.json.return_value = create_billing_products_response()
-
- return mock
-
- mock_request.side_effect = mock_implementation
- self.organization.usage = {"events": {"limit": 1000000, "usage": 1000, "todays_usage": 1100000}}
- self.organization.save()
-
- res = self.client.get("/api/billing")
- assert res.status_code == 200
-
@patch("ee.api.billing.requests.get")
def test_organization_usage_count_with_demo_project(self, mock_request, *args):
def mock_implementation(url: str, headers: Any = None, params: Any = None) -> MagicMock:
diff --git a/ee/billing/billing_manager.py b/ee/billing/billing_manager.py
index 2f35a7be3b92e..4f735d4890b3d 100644
--- a/ee/billing/billing_manager.py
+++ b/ee/billing/billing_manager.py
@@ -1,7 +1,6 @@
from datetime import datetime, timedelta
-from decimal import Decimal
from enum import Enum
-from typing import Any, Optional, Union, cast
+from typing import Any, Optional, cast
import jwt
import requests
@@ -11,7 +10,7 @@
from rest_framework.exceptions import NotAuthenticated
from sentry_sdk import capture_exception
-from ee.billing.billing_types import BillingStatus, Tier
+from ee.billing.billing_types import BillingStatus
from ee.billing.quota_limiting import set_org_usage_summary, sync_org_quota_limits
from ee.models import License
from ee.settings import BILLING_SERVICE_URL
@@ -58,61 +57,6 @@ def handle_billing_service_error(res: requests.Response, valid_codes=(200, 404,
raise Exception(f"Billing service returned bad status code: {res.status_code}", f"body:", res.text)
-def compute_usage_per_tier(limited_usage: int, projected_usage: int, tiers):
- remaining_usage = limited_usage
- remaining_projected_usage = projected_usage or 0
- previous_tier: Optional[dict[str, Any]] = None
- tier_max_usage: Union[int, float] = 0
-
- result: list[Tier] = []
- for tier in tiers:
- if previous_tier and previous_tier.get("up_to"):
- previous_tier_up_to = previous_tier["up_to"]
- else:
- previous_tier_up_to = 0
-
- if tier.get("up_to"):
- tier_max_usage = tier["up_to"] - previous_tier_up_to
- else:
- tier_max_usage = float("inf")
-
- flat_amount_usd = Decimal(tier.get("flat_amount_usd") or 0)
- unit_amount_usd = Decimal(tier.get("unit_amount_usd") or 0)
- usage_this_tier = int(min(remaining_usage, tier_max_usage))
- remaining_usage -= usage_this_tier
- current_amount_usd = Decimal(unit_amount_usd * usage_this_tier + flat_amount_usd).quantize(Decimal("0.01"))
- previous_tier = tier
- if projected_usage:
- projected_usage_this_tier = int(min(remaining_projected_usage, tier_max_usage))
- remaining_projected_usage -= projected_usage_this_tier
- projected_amount_usd = Decimal(unit_amount_usd * projected_usage_this_tier + flat_amount_usd).quantize(
- Decimal("0.01")
- )
- else:
- projected_usage_this_tier = None
- projected_amount_usd = None
-
- result.append(
- Tier(
- flat_amount_usd=str(flat_amount_usd),
- unit_amount_usd=str(unit_amount_usd),
- up_to=tier.get("up_to", None),
- current_amount_usd=str(current_amount_usd),
- current_usage=usage_this_tier,
- projected_usage=projected_usage_this_tier,
- projected_amount_usd=str(projected_amount_usd),
- )
- )
- return result
-
-
-def sum_total_across_tiers(tiers):
- total = Decimal(0)
- for tier in tiers:
- total += Decimal(tier["current_amount_usd"])
- return total
-
-
class BillingManager:
license: Optional[License]
@@ -164,50 +108,6 @@ def get_billing(self, organization: Optional[Organization], plan_keys: Optional[
product["current_usage"] = current_usage
product["percentage_usage"] = current_usage / usage_limit if usage_limit else 0
-
- # Also update the tiers
- if product.get("tiers"):
- usage_limit = product_usage.get("limit")
- limited_usage = 0
- # If the usage has already exceeded the billing limit, don't increment
- # today's usage
- if usage_limit is not None and billing_reported_usage > usage_limit:
- limited_usage = billing_reported_usage
- else:
- limited_usage = current_usage
-
- product["tiers"] = compute_usage_per_tier(
- limited_usage, product["projected_usage"], product["tiers"]
- )
- product["current_amount_usd"] = str(sum_total_across_tiers(product["tiers"]))
-
- # Update the add on tiers
- # TODO: enhanced_persons: make sure this updates properly for addons with different usage keys
- for addon in product.get("addons"):
- if not addon.get("subscribed"):
- continue
- addon_usage_key = addon.get("usage_key")
- if not usage_key:
- continue
- if addon_usage_key != usage_key:
- usage = response.get("usage_summary", {}).get(addon_usage_key, {})
- usage_limit = usage.get("limit")
- billing_reported_usage = usage.get("usage") or 0
- if product_usage.get("todays_usage"):
- todays_usage = product_usage["todays_usage"]
- current_usage = billing_reported_usage + todays_usage
- addon["current_usage"] = current_usage
-
- limited_usage = 0
- # If the usage has already exceeded the billing limit, don't increment
- # today's usage
- if usage_limit is not None and billing_reported_usage > usage_limit:
- limited_usage = billing_reported_usage
- else:
- # Otherwise, do increment toady's usage
- limited_usage = current_usage
- addon["tiers"] = compute_usage_per_tier(limited_usage, addon["projected_usage"], addon["tiers"])
- addon["current_amount_usd"] = str(sum_total_across_tiers(addon["tiers"]))
else:
products = self.get_default_products(organization)
response = {
diff --git a/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png b/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png
index 0510a04db24d3..fb9a44bbb9c09 100644
Binary files a/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png differ
diff --git a/frontend/src/scenes/billing/Billing.tsx b/frontend/src/scenes/billing/Billing.tsx
index 8bd8d5305432a..c6cb267ffd255 100644
--- a/frontend/src/scenes/billing/Billing.tsx
+++ b/frontend/src/scenes/billing/Billing.tsx
@@ -145,7 +145,7 @@ export function Billing(): JSX.Element {
{billing?.has_active_subscription && (
<>