Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit bb3eb24

Browse files
Merge remote-tracking branch 'origin/main' into sshin/microdeposits
2 parents 8ed50e7 + 354b640 commit bb3eb24

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+993
-376
lines changed

api/internal/owner/serializers.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@
77
from rest_framework import serializers
88
from rest_framework.exceptions import PermissionDenied
99
from shared.plan.constants import (
10-
PAID_PLANS,
11-
SENTRY_PAID_USER_PLAN_REPRESENTATIONS,
1210
TEAM_PLAN_MAX_USERS,
13-
TEAM_PLAN_REPRESENTATIONS,
11+
TierName,
1412
)
1513
from shared.plan.service import PlanService
1614

17-
from codecov_auth.models import Owner
15+
from codecov_auth.models import Owner, Plan
1816
from services.billing import BillingService
1917
from services.sentry import send_user_webhook as send_sentry_webhook
2018

@@ -137,11 +135,6 @@ def validate_value(self, value: str) -> str:
137135
plan["value"] for plan in plan_service.available_plans(current_owner)
138136
]
139137
if value not in plan_values:
140-
if value in SENTRY_PAID_USER_PLAN_REPRESENTATIONS:
141-
log.warning(
142-
"Non-Sentry user attempted to transition to Sentry plan",
143-
extra=dict(owner_id=current_owner.pk, plan=value),
144-
)
145138
raise serializers.ValidationError(
146139
f"Invalid value for plan: {value}; must be one of {plan_values}"
147140
)
@@ -154,8 +147,17 @@ def validate(self, plan: Dict[str, Any]) -> Dict[str, Any]:
154147
detail="You cannot update your plan manually, for help or changes to plan, connect with [email protected]"
155148
)
156149

150+
active_plans = Plan.objects.select_related("tier").filter(
151+
paid_plan=True, is_active=True
152+
)
153+
154+
active_plan_names = set(active_plans.values_list("name", flat=True))
155+
team_tier_plans = active_plans.filter(
156+
tier__tier_name=TierName.TEAM.value
157+
).values_list("name", flat=True)
158+
157159
# Validate quantity here because we need access to whole plan object
158-
if plan["value"] in PAID_PLANS:
160+
if plan["value"] in active_plan_names:
159161
if "quantity" not in plan:
160162
raise serializers.ValidationError(
161163
"Field 'quantity' required for updating to paid plans"
@@ -184,7 +186,7 @@ def validate(self, plan: Dict[str, Any]) -> Dict[str, Any]:
184186
"Quantity or plan for paid plan must be different from the existing one"
185187
)
186188
if (
187-
plan["value"] in TEAM_PLAN_REPRESENTATIONS
189+
plan["value"] in team_tier_plans
188190
and plan["quantity"] > TEAM_PLAN_MAX_USERS
189191
):
190192
raise serializers.ValidationError(
@@ -219,7 +221,7 @@ def get_plan(self, phase: Dict[str, Any]) -> str:
219221
plan_name = list(stripe_plan_dict.keys())[
220222
list(stripe_plan_dict.values()).index(plan_id)
221223
]
222-
marketing_plan_name = PAID_PLANS[plan_name].billing_rate
224+
marketing_plan_name = Plan.objects.get(name=plan_name).marketing_name
223225
return marketing_plan_name
224226

225227
def get_quantity(self, phase: Dict[str, Any]) -> int:
@@ -342,7 +344,11 @@ def update(self, instance: Owner, validated_data: Dict[str, Any]) -> object:
342344
instance, desired_plan
343345
)
344346

345-
if desired_plan["value"] in SENTRY_PAID_USER_PLAN_REPRESENTATIONS:
347+
sentry_plans = Plan.objects.filter(
348+
tier__tier_name=TierName.SENTRY.value, is_active=True
349+
).values_list("name", flat=True)
350+
351+
if desired_plan["value"] in sentry_plans:
346352
current_owner = self.context["view"].request.current_owner
347353
send_sentry_webhook(current_owner, instance)
348354

api/internal/tests/test_pagination.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
from rest_framework.reverse import reverse
22
from rest_framework.test import APITestCase
3+
from shared.django_apps.codecov_auth.tests.factories import PlanFactory, TierFactory
34
from shared.django_apps.core.tests.factories import OwnerFactory
5+
from shared.plan.constants import TierName
46

57
from utils.test_utils import Client
68

79

810
class PageNumberPaginationTests(APITestCase):
911
def setUp(self):
1012
self.client = Client()
11-
self.owner = OwnerFactory(plan="users-free", plan_user_count=5)
13+
tier = TierFactory(tier_name=TierName.BASIC.value)
14+
plan = PlanFactory(tier=tier, is_active=True)
15+
self.owner = OwnerFactory(plan=plan.name, plan_user_count=5)
1216
self.users = [
1317
OwnerFactory(organizations=[self.owner.ownerid]),
1418
OwnerFactory(organizations=[self.owner.ownerid]),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
interactions:
2+
- request:
3+
body: billing_address_collection=required&payment_method_collection=if_required&client_reference_id=65&success_url=http%3A%2F%2Flocalhost%3A3000%2Fplan%2Fgh%2Fjustin47%3Fsuccess&cancel_url=http%3A%2F%2Flocalhost%3A3000%2Fplan%2Fgh%2Fjustin47%3Fcancel&customer=1000&mode=subscription&line_items[0][price]=price_1OCM0gGlVGuVgOrkWDYEBtSL&line_items[0][quantity]=11&subscription_data[metadata][service]=github&subscription_data[metadata][obo_organization]=65&subscription_data[metadata][username]=justin47&subscription_data[metadata][obo_name]=Kelly+Williams&subscription_data[metadata][obo_email]=christopher27%40lopez-welch.com&subscription_data[metadata][obo]=65&tax_id_collection[enabled]=True&customer_update[name]=auto&customer_update[address]=auto
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
Content-Length:
12+
- '744'
13+
Content-Type:
14+
- application/x-www-form-urlencoded
15+
Idempotency-Key:
16+
- 2e7e12e1-9051-4766-b947-1abe985b5e98
17+
Stripe-Version:
18+
- 2024-12-18.acacia
19+
User-Agent:
20+
- Stripe/v1 PythonBindings/11.4.1
21+
X-Stripe-Client-User-Agent:
22+
- '{"bindings_version": "11.4.1", "lang": "python", "publisher": "stripe", "httplib":
23+
"requests", "lang_version": "3.12.8", "platform": "Linux-6.10.14-linuxkit-aarch64-with-glibc2.36",
24+
"uname": "Linux 35c9e7c77efc 6.10.14-linuxkit #1 SMP Fri Nov 29 17:22:03 UTC
25+
2024 aarch64 "}'
26+
method: POST
27+
uri: https://api.stripe.com/v1/checkout/sessions
28+
response:
29+
body:
30+
string: "{\n \"error\": {\n \"code\": \"resource_missing\",\n \"doc_url\":
31+
\"https://stripe.com/docs/error-codes/resource-missing\",\n \"message\":
32+
\"No such customer: '1000'\",\n \"param\": \"customer\",\n \"request_log_url\":
33+
\"https://dashboard.stripe.com/test/logs/req_oevRZUbMiaT1kM?t=1737668618\",\n
34+
\ \"type\": \"invalid_request_error\"\n }\n}\n"
35+
headers:
36+
Access-Control-Allow-Credentials:
37+
- 'true'
38+
Access-Control-Allow-Methods:
39+
- GET, HEAD, PUT, PATCH, POST, DELETE
40+
Access-Control-Allow-Origin:
41+
- '*'
42+
Access-Control-Expose-Headers:
43+
- Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required,
44+
X-Stripe-Privileged-Session-Required
45+
Access-Control-Max-Age:
46+
- '300'
47+
Cache-Control:
48+
- no-cache, no-store
49+
Connection:
50+
- keep-alive
51+
Content-Length:
52+
- '325'
53+
Content-Security-Policy:
54+
- base-uri 'none'; default-src 'none'; form-action 'none'; frame-ancestors 'none';
55+
img-src 'self'; script-src 'self' 'report-sample'; style-src 'self'; upgrade-insecure-requests;
56+
report-uri https://q.stripe.com/csp-violation?q=ieXMYrsoTw4hsNfwL6RhDPN6zQnVwnAAc0QsFb8i8xtl4N7V94VZzDmgDhKzWxW8mHRRg08-d5GW6oHr
57+
Content-Type:
58+
- application/json
59+
Cross-Origin-Opener-Policy-Report-Only:
60+
- same-origin; report-to="coop"
61+
Date:
62+
- Thu, 23 Jan 2025 21:43:38 GMT
63+
Idempotency-Key:
64+
- 2e7e12e1-9051-4766-b947-1abe985b5e98
65+
Original-Request:
66+
- req_oevRZUbMiaT1kM
67+
Report-To:
68+
- '{"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report"}],"include_subdomains":true}'
69+
Reporting-Endpoints:
70+
- coop="https://q.stripe.com/coop-report"
71+
Request-Id:
72+
- req_oevRZUbMiaT1kM
73+
Server:
74+
- nginx
75+
Strict-Transport-Security:
76+
- max-age=63072000; includeSubDomains; preload
77+
Stripe-Version:
78+
- 2024-12-18.acacia
79+
Vary:
80+
- Origin
81+
X-Content-Type-Options:
82+
- nosniff
83+
X-Stripe-Priority-Routing-Enabled:
84+
- 'true'
85+
X-Stripe-Routing-Context-Priority-Tier:
86+
- api-testmode
87+
X-Wc:
88+
- AB
89+
status:
90+
code: 400
91+
message: Bad Request
92+
version: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
interactions:
2+
- request:
3+
body: billing_address_collection=required&payment_method_collection=if_required&client_reference_id=93&success_url=http%3A%2F%2Flocalhost%3A3000%2Fplan%2Fgh%2Fjocelyn62%3Fsuccess&cancel_url=http%3A%2F%2Flocalhost%3A3000%2Fplan%2Fgh%2Fjocelyn62%3Fcancel&customer=1000&mode=subscription&line_items[0][price]=price_1OCM0gGlVGuVgOrkWDYEBtSL&line_items[0][quantity]=11&subscription_data[metadata][service]=github&subscription_data[metadata][obo_organization]=93&subscription_data[metadata][username]=jocelyn62&subscription_data[metadata][obo_name]=Crystal+Schmitt&subscription_data[metadata][obo_email]=smithamanda%40flowers.biz&subscription_data[metadata][obo]=93&tax_id_collection[enabled]=True&customer_update[name]=auto&customer_update[address]=auto
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
Content-Length:
12+
- '742'
13+
Content-Type:
14+
- application/x-www-form-urlencoded
15+
Idempotency-Key:
16+
- 26000f5a-feb8-4647-ac57-32a5e5ff8729
17+
Stripe-Version:
18+
- 2024-12-18.acacia
19+
User-Agent:
20+
- Stripe/v1 PythonBindings/11.4.1
21+
X-Stripe-Client-Telemetry:
22+
- '{"last_request_metrics": {"request_id": "req_AaY8IvHbbSDcvz", "request_duration_ms":
23+
2}}'
24+
X-Stripe-Client-User-Agent:
25+
- '{"bindings_version": "11.4.1", "lang": "python", "publisher": "stripe", "httplib":
26+
"requests", "lang_version": "3.12.8", "platform": "Linux-6.10.14-linuxkit-aarch64-with-glibc2.36",
27+
"uname": "Linux 35c9e7c77efc 6.10.14-linuxkit #1 SMP Fri Nov 29 17:22:03 UTC
28+
2024 aarch64 "}'
29+
method: POST
30+
uri: https://api.stripe.com/v1/checkout/sessions
31+
response:
32+
body:
33+
string: "{\n \"error\": {\n \"code\": \"resource_missing\",\n \"doc_url\":
34+
\"https://stripe.com/docs/error-codes/resource-missing\",\n \"message\":
35+
\"No such customer: '1000'\",\n \"param\": \"customer\",\n \"request_log_url\":
36+
\"https://dashboard.stripe.com/test/logs/req_k8lY68XdxWIFHo?t=1737668619\",\n
37+
\ \"type\": \"invalid_request_error\"\n }\n}\n"
38+
headers:
39+
Access-Control-Allow-Credentials:
40+
- 'true'
41+
Access-Control-Allow-Methods:
42+
- GET, HEAD, PUT, PATCH, POST, DELETE
43+
Access-Control-Allow-Origin:
44+
- '*'
45+
Access-Control-Expose-Headers:
46+
- Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required,
47+
X-Stripe-Privileged-Session-Required
48+
Access-Control-Max-Age:
49+
- '300'
50+
Cache-Control:
51+
- no-cache, no-store
52+
Connection:
53+
- keep-alive
54+
Content-Length:
55+
- '325'
56+
Content-Security-Policy:
57+
- base-uri 'none'; default-src 'none'; form-action 'none'; frame-ancestors 'none';
58+
img-src 'self'; script-src 'self' 'report-sample'; style-src 'self'; upgrade-insecure-requests;
59+
report-uri https://q.stripe.com/csp-violation?q=1vgSiZ6UPd0qoBXo1Mjsk02GXFGP3M7PXsjua2jiowWQKm8jByxTHbhTeRirsQsrZ7jscQLjXtdCc_sh
60+
Content-Type:
61+
- application/json
62+
Cross-Origin-Opener-Policy-Report-Only:
63+
- same-origin; report-to="coop"
64+
Date:
65+
- Thu, 23 Jan 2025 21:43:39 GMT
66+
Idempotency-Key:
67+
- 26000f5a-feb8-4647-ac57-32a5e5ff8729
68+
Original-Request:
69+
- req_k8lY68XdxWIFHo
70+
Report-To:
71+
- '{"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report"}],"include_subdomains":true}'
72+
Reporting-Endpoints:
73+
- coop="https://q.stripe.com/coop-report"
74+
Request-Id:
75+
- req_k8lY68XdxWIFHo
76+
Server:
77+
- nginx
78+
Strict-Transport-Security:
79+
- max-age=63072000; includeSubDomains; preload
80+
Stripe-Version:
81+
- 2024-12-18.acacia
82+
Vary:
83+
- Origin
84+
X-Content-Type-Options:
85+
- nosniff
86+
X-Stripe-Priority-Routing-Enabled:
87+
- 'true'
88+
X-Stripe-Routing-Context-Priority-Tier:
89+
- api-testmode
90+
X-Wc:
91+
- AB
92+
status:
93+
code: 400
94+
message: Bad Request
95+
version: 1

0 commit comments

Comments
 (0)