diff --git a/cypress/fixtures/api/billing/billing-unsubscribed-product-analytics.json b/cypress/fixtures/api/billing/billing-unsubscribed-product-analytics.json index fe27adcfb809b..d6ed0f5b55d8f 100644 --- a/cypress/fixtures/api/billing/billing-unsubscribed-product-analytics.json +++ b/cypress/fixtures/api/billing/billing-unsubscribed-product-analytics.json @@ -1955,5 +1955,5 @@ "discount_amount_usd": null, "amount_off_expires_at": null, "never_drop_data": null, - "stripe_portal_url": "https://billing.stripe.com/p/session/test_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9PdXhKRW1kYVNRb3BOcFpVVHl6emVCUjdSUDJrQTJs0100akrliUs0" + "stripe_portal_url": "http://localhost:8000/api/billing/portal" } diff --git a/cypress/fixtures/api/billing/billing-unsubscribed.json b/cypress/fixtures/api/billing/billing-unsubscribed.json index fc92df7978e51..114750954906a 100644 --- a/cypress/fixtures/api/billing/billing-unsubscribed.json +++ b/cypress/fixtures/api/billing/billing-unsubscribed.json @@ -3657,5 +3657,5 @@ "session_replay": 3, "product_analytics": 3 }, - "stripe_portal_url": "https://billing.stripe.com/p/session/test_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9QaEVaQ0hCTUE0aE8wUFhlVWVqd29MaElGd3lwRjFa010044U4IxJp" + "stripe_portal_url": "http://localhost:8000/api/billing/portal" } diff --git a/cypress/fixtures/api/billing/billing.json b/cypress/fixtures/api/billing/billing.json index cf60c67b4fea1..4085850755696 100644 --- a/cypress/fixtures/api/billing/billing.json +++ b/cypress/fixtures/api/billing/billing.json @@ -3777,5 +3777,5 @@ "session_replay": 3, "product_analytics": 3 }, - "stripe_portal_url": "https://billing.stripe.com/p/session/test_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9QaEVpVHFJNXdKYk9DaG04SVhMaUV4TDlxOTR1WEZi0100SMJCDr2e" + "stripe_portal_url": "http://localhost:8000/api/billing/portal" } diff --git a/ee/api/billing.py b/ee/api/billing.py index d139cbf3dda47..9b6cf9a4ecf9e 100644 --- a/ee/api/billing.py +++ b/ee/api/billing.py @@ -180,6 +180,20 @@ def deactivate(self, request: Request, *args: Any, **kwargs: Any) -> HttpRespons return self.list(request, *args, **kwargs) + @action(methods=["GET"], detail=False) + def portal(self, request: Request, *args: Any, **kwargs: Any) -> HttpResponse: + license = get_cached_instance_license() + if not license: + return Response( + {"sucess": True}, + status=status.HTTP_200_OK, + ) + + organization = self._get_org_required() + + res = BillingManager(license)._get_stripe_portal_url(organization) + return redirect(res) + @action(methods=["GET"], detail=False) def get_invoices(self, request: Request, *args: Any, **kwargs: Any) -> HttpResponse: license = get_cached_instance_license() diff --git a/ee/api/test/test_billing.py b/ee/api/test/test_billing.py index c54b8bdafe1a9..9f152206ffefa 100644 --- a/ee/api/test/test_billing.py +++ b/ee/api/test/test_billing.py @@ -359,7 +359,7 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma "available_product_features": [], "custom_limits_usd": {}, "has_active_subscription": True, - "stripe_portal_url": "https://billing.stripe.com/p/session/test_1234", + "stripe_portal_url": "http://localhost:8000/api/billing/portal", "current_total_amount_usd": "100.00", "deactivated": False, "products": [ @@ -562,7 +562,7 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma "free_trial_until": None, "current_total_amount_usd": "0.00", "deactivated": False, - "stripe_portal_url": "https://billing.stripe.com/p/session/test_1234", + "stripe_portal_url": "http://localhost:8000/api/billing/portal", } @patch("ee.api.billing.requests.get") @@ -839,6 +839,18 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma assert self.organization.customer_trust_scores == {"recordings": 0, "events": 15, "rows_synced": 0} +class TestPortalBillingAPI(APILicensedTest): + @patch("ee.api.billing.requests.get") + def test_portal_success(self, mock_request): + mock_request.return_value.status_code = 200 + mock_request.return_value.json.return_value = {"url": "https://billing.stripe.com/p/session/test_1234"} + + response = self.client.get("/api/billing/portal") + + self.assertEqual(response.status_code, status.HTTP_302_FOUND) + self.assertIn("https://billing.stripe.com/p/session/test_1234", response.url) + + class TestActivateBillingAPI(APILicensedTest): def test_activate_success(self): url = "/api/billing/activate" diff --git a/ee/billing/billing_manager.py b/ee/billing/billing_manager.py index 4f735d4890b3d..904c6b63b9f17 100644 --- a/ee/billing/billing_manager.py +++ b/ee/billing/billing_manager.py @@ -1,3 +1,4 @@ +from django.conf import settings from datetime import datetime, timedelta from enum import Enum from typing import Any, Optional, cast @@ -85,8 +86,7 @@ def get_billing(self, organization: Optional[Organization], plan_keys: Optional[ products = self.get_default_products(organization) response["products"] = products["products"] - stripe_portal_url = self._get_stripe_portal_url(organization) - response["stripe_portal_url"] = stripe_portal_url + response["stripe_portal_url"] = f"{settings.SITE_URL}/api/billing/portal" # Extend the products with accurate usage_limit info for product in response["products"]: @@ -207,7 +207,7 @@ def _get_billing(self, organization: Organization) -> BillingStatus: return data - def _get_stripe_portal_url(self, organization: Organization) -> BillingStatus: + def _get_stripe_portal_url(self, organization: Organization) -> str: """ Retrieves stripe protal url """ diff --git a/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png b/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png index 3ebbaaaf6a8b0..5d30316a8b712 100644 Binary files a/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png and b/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png differ diff --git a/frontend/__snapshots__/replay-player-success--recent-recordings--light.png b/frontend/__snapshots__/replay-player-success--recent-recordings--light.png index 5dab048344667..46163479cd849 100644 Binary files a/frontend/__snapshots__/replay-player-success--recent-recordings--light.png and b/frontend/__snapshots__/replay-player-success--recent-recordings--light.png differ diff --git a/frontend/src/mocks/fixtures/_billing_unsubscribed.json b/frontend/src/mocks/fixtures/_billing_unsubscribed.json index 9c97b6747e4ee..669bfe8cee355 100644 --- a/frontend/src/mocks/fixtures/_billing_unsubscribed.json +++ b/frontend/src/mocks/fixtures/_billing_unsubscribed.json @@ -1872,5 +1872,5 @@ "discount_amount_usd": null, "amount_off_expires_at": null, "never_drop_data": null, - "stripe_portal_url": "https://billing.stripe.com/p/session/test_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9QN0ltZVQ3RmpLbTZacXgzYWo3Q0FFbFpITHZydlpK0100iKmkfAZi" + "stripe_portal_url": "http://localhost:8000/api/billing/portal" } diff --git a/frontend/src/mocks/fixtures/_billing_with_100_percent_discount.json b/frontend/src/mocks/fixtures/_billing_with_100_percent_discount.json index 464b9b1356771..7aec42b4c008e 100644 --- a/frontend/src/mocks/fixtures/_billing_with_100_percent_discount.json +++ b/frontend/src/mocks/fixtures/_billing_with_100_percent_discount.json @@ -2769,5 +2769,5 @@ ] } ], - "stripe_portal_url": "https://billing.stripe.com/p/session/XYZ" + "stripe_portal_url": "http://localhost:8000/api/billing/portal" } diff --git a/frontend/src/mocks/fixtures/_billing_with_billing_limits.json b/frontend/src/mocks/fixtures/_billing_with_billing_limits.json index 2e156c689ab0d..dc9a26df5389a 100644 --- a/frontend/src/mocks/fixtures/_billing_with_billing_limits.json +++ b/frontend/src/mocks/fixtures/_billing_with_billing_limits.json @@ -4683,5 +4683,5 @@ "session_replay": 3, "product_analytics": 3 }, - "stripe_portal_url": "https://billing.stripe.com/p/session/test_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9RNE9kdEg5elRmWE1wZkY1SGlsTFFFQXRKRmVkdnpJ0100M2MwqzHs" + "stripe_portal_url": "http://localhost:8000/api/billing/portal" } diff --git a/frontend/src/mocks/fixtures/_billing_with_discount.json b/frontend/src/mocks/fixtures/_billing_with_discount.json index 4c393f5c190bd..395b777ed8e48 100644 --- a/frontend/src/mocks/fixtures/_billing_with_discount.json +++ b/frontend/src/mocks/fixtures/_billing_with_discount.json @@ -2767,5 +2767,5 @@ ] } ], - "stripe_portal_url": "https://billing.stripe.com/p/session/XYZ" + "stripe_portal_url": "http://localhost:8000/api/billing/portal" } diff --git a/frontend/src/mocks/fixtures/_billing_with_flat_fee.json b/frontend/src/mocks/fixtures/_billing_with_flat_fee.json index 5661d07eda436..fe522a5da1d52 100644 --- a/frontend/src/mocks/fixtures/_billing_with_flat_fee.json +++ b/frontend/src/mocks/fixtures/_billing_with_flat_fee.json @@ -1447,5 +1447,5 @@ "discount_amount_usd": null, "amount_off_expires_at": null, "never_drop_data": false, - "stripe_portal_url": "https://billing.stripe.com/p/session/live_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9PeG9wUmZuUFpmcTlvaWVjeUd0ZlhFN2hHbjJUWDR30100sm6GDMjx" + "stripe_portal_url": "http://localhost:8000/api/billing/portal" } diff --git a/frontend/src/scenes/billing/Billing.tsx b/frontend/src/scenes/billing/Billing.tsx index 084dd201ced20..ef2584c86cfb0 100644 --- a/frontend/src/scenes/billing/Billing.tsx +++ b/frontend/src/scenes/billing/Billing.tsx @@ -220,6 +220,7 @@ export function Billing(): JSX.Element { htmlType="submit" to={billing.stripe_portal_url} disableClientSideRouting + targetBlank center > Manage card details and view past invoices