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

Commit fd0f925

Browse files
cleanup and add tests
1 parent 198f23e commit fd0f925

File tree

7 files changed

+97
-33
lines changed

7 files changed

+97
-33
lines changed

api/internal/owner/views.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -131,29 +131,6 @@ def update_billing_address(self, request, *args, **kwargs):
131131
billing.update_billing_address(owner, name, billing_address=formatted_address)
132132
return Response(self.get_serializer(owner).data)
133133

134-
@action(detail=False, methods=["post"])
135-
@stripe_safe
136-
def setup_intent(self, request, *args, **kwargs):
137-
"""
138-
Create a Stripe SetupIntent to securely collect payment details.
139-
140-
Returns:
141-
Response with SetupIntent client_secret for frontend payment method setup.
142-
143-
Raises:
144-
ValidationError: If SetupIntent creation fails
145-
"""
146-
try:
147-
billing = BillingService(requesting_user=request.current_owner)
148-
client_secret = billing.create_setup_intent(self.owner)
149-
return Response({"client_secret": client_secret})
150-
except Exception as e:
151-
log.error(
152-
f"Error getting setup intent for owner {self.owner.ownerid}",
153-
extra={"error": str(e)},
154-
)
155-
raise ValidationError(detail="Unable to create setup intent")
156-
157134

158135
class UsersOrderingFilter(filters.OrderingFilter):
159136
def get_valid_fields(self, queryset, view, context=None):

api/internal/tests/views/test_account_viewset.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,46 @@ def test_update_email_address(self, modify_customer_mock, retrieve_mock):
12281228
self.current_owner.stripe_customer_id, email=new_email
12291229
)
12301230

1231+
@patch("services.billing.stripe.Subscription.retrieve")
1232+
@patch("services.billing.stripe.Customer.modify")
1233+
@patch("services.billing.stripe.PaymentMethod.modify")
1234+
@patch("services.billing.stripe.Customer.retrieve")
1235+
def test_update_email_address_with_propagate(
1236+
self,
1237+
customer_retrieve_mock,
1238+
payment_method_mock,
1239+
modify_customer_mock,
1240+
retrieve_mock,
1241+
):
1242+
self.current_owner.stripe_customer_id = "flsoe"
1243+
self.current_owner.stripe_subscription_id = "djfos"
1244+
self.current_owner.save()
1245+
1246+
payment_method_id = "pm_123"
1247+
customer_retrieve_mock.return_value = {
1248+
"invoice_settings": {"default_payment_method": payment_method_id}
1249+
}
1250+
1251+
new_email = "[email protected]"
1252+
kwargs = {
1253+
"service": self.current_owner.service,
1254+
"owner_username": self.current_owner.username,
1255+
}
1256+
data = {"new_email": new_email, "should_propagate_to_payment_methods": True}
1257+
url = reverse("account_details-update-email", kwargs=kwargs)
1258+
response = self.client.patch(url, data=data, format="json")
1259+
assert response.status_code == status.HTTP_200_OK
1260+
1261+
modify_customer_mock.assert_called_once_with(
1262+
self.current_owner.stripe_customer_id, email=new_email
1263+
)
1264+
customer_retrieve_mock.assert_called_once_with(
1265+
self.current_owner.stripe_customer_id
1266+
)
1267+
payment_method_mock.assert_called_once_with(
1268+
payment_method_id, billing_details={"email": new_email}
1269+
)
1270+
12311271
def test_update_billing_address_without_body(self):
12321272
kwargs = {
12331273
"service": self.current_owner.service,

codecov_auth/commands/owner/interactors/create_stripe_setup_intent.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
log = logging.getLogger(__name__)
1313

14+
1415
class CreateStripeSetupIntentInteractor(BaseInteractor):
1516
def validate(self, owner_obj: Owner) -> None:
1617
if not self.current_user.is_authenticated:

graphql_api/tests/mutation/test_create_stripe_setup_intent.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from unittest.mock import patch
2+
23
from django.test import TransactionTestCase
34
from shared.django_apps.core.tests.factories import OwnerFactory
45

5-
from codecov_auth.models import Session
66
from graphql_api.tests.helper import GraphQLTestHelper
77

88
query = """
@@ -23,10 +23,43 @@ def setUp(self):
2323

2424
def test_when_unauthenticated(self):
2525
data = self.gql_request(query, variables={"input": {"owner": "somename"}})
26-
assert data["createStripeSetupIntent"]["error"]["__typename"] == "UnauthenticatedError"
26+
assert (
27+
data["createStripeSetupIntent"]["error"]["__typename"]
28+
== "UnauthenticatedError"
29+
)
30+
31+
def test_when_unauthorized(self):
32+
other_owner = OwnerFactory(username="other-user")
33+
data = self.gql_request(
34+
query,
35+
owner=self.owner,
36+
variables={"input": {"owner": other_owner.username}},
37+
)
38+
assert (
39+
data["createStripeSetupIntent"]["error"]["__typename"]
40+
== "UnauthorizedError"
41+
)
42+
43+
@patch("services.billing.stripe.SetupIntent.create")
44+
def test_when_validation_error(self, setup_intent_create_mock):
45+
setup_intent_create_mock.side_effect = Exception("Some error")
46+
data = self.gql_request(
47+
query, owner=self.owner, variables={"input": {"owner": self.owner.username}}
48+
)
49+
assert (
50+
data["createStripeSetupIntent"]["error"]["__typename"] == "ValidationError"
51+
)
52+
53+
def test_when_owner_not_found(self):
54+
data = self.gql_request(
55+
query, owner=self.owner, variables={"input": {"owner": "nonexistent-user"}}
56+
)
57+
assert (
58+
data["createStripeSetupIntent"]["error"]["__typename"] == "ValidationError"
59+
)
2760

2861
@patch("services.billing.stripe.SetupIntent.create")
29-
def test_when_authenticated(self, setup_intent_create_mock):
62+
def test_success(self, setup_intent_create_mock):
3063
setup_intent_create_mock.return_value = {"client_secret": "test-client-secret"}
3164
data = self.gql_request(
3265
query, owner=self.owner, variables={"input": {"owner": self.owner.username}}

graphql_api/types/mutation/create_stripe_setup_intent/create_stripe_setup_intent.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
union CreateStripeSetupIntentError = UnauthenticatedError | ValidationError
1+
union CreateStripeSetupIntentError = UnauthenticatedError | UnauthorizedError | ValidationError
22

33
type CreateStripeSetupIntentPayload {
44
error: CreateStripeSetupIntentError

services/billing.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,12 @@ def update_payment_method(self, owner: Owner, payment_method):
584584
)
585585

586586
@_log_stripe_error
587-
def update_email_address(self, owner: Owner, email_address: str, should_propagate_to_payment_methods: bool = False):
587+
def update_email_address(
588+
self,
589+
owner: Owner,
590+
email_address: str,
591+
should_propagate_to_payment_methods: bool = False,
592+
):
588593
if not re.fullmatch(r"[^@]+@[^@]+\.[^@]+", email_address):
589594
return None
590595

@@ -603,7 +608,7 @@ def update_email_address(self, owner: Owner, email_address: str, should_propagat
603608
try:
604609
default_payment_method = stripe.Customer.retrieve(
605610
owner.stripe_customer_id
606-
).invoice_settings.default_payment_method
611+
)["invoice_settings"]["default_payment_method"]
607612

608613
stripe.PaymentMethod.modify(
609614
default_payment_method,
@@ -701,10 +706,11 @@ def create_setup_intent(self, owner: Owner) -> stripe.SetupIntent:
701706
),
702707
)
703708
return stripe.SetupIntent.create(
704-
payment_method_types=['card', 'us_bank_account'],
709+
payment_method_configuration=settings.STRIPE_PAYMENT_METHOD_CONFIGURATION_ID,
705710
customer=owner.stripe_customer_id,
706711
)
707712

713+
708714
class EnterprisePaymentService(AbstractPaymentService):
709715
# enterprise has no payments setup so these are all noops
710716

@@ -805,14 +811,21 @@ def update_payment_method(self, owner, payment_method):
805811
"""
806812
return self.payment_service.update_payment_method(owner, payment_method)
807813

808-
def update_email_address(self, owner: Owner, email_address: str, should_propagate_to_payment_methods: bool = False):
814+
def update_email_address(
815+
self,
816+
owner: Owner,
817+
email_address: str,
818+
should_propagate_to_payment_methods: bool = False,
819+
):
809820
"""
810821
Takes an owner and a new email. Email is a string coming directly from
811822
the front-end. If the owner has a payment id and if it's a valid email,
812823
the payment service will update the email address in the upstream service.
813824
Otherwise returns None.
814825
"""
815-
return self.payment_service.update_email_address(owner, email_address, should_propagate_to_payment_methods)
826+
return self.payment_service.update_email_address(
827+
owner, email_address, should_propagate_to_payment_methods
828+
)
816829

817830
def update_billing_address(self, owner: Owner, name: str, billing_address):
818831
"""

services/tests/test_billing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1538,6 +1538,7 @@ def test_create_checkout_session_with_no_stripe_customer_id(
15381538
tax_id_collection={"enabled": True},
15391539
customer_update=None,
15401540
)
1541+
15411542
@patch("services.billing.stripe.checkout.Session.create")
15421543
def test_create_checkout_session_with_stripe_customer_id(
15431544
self, create_checkout_session_mock
@@ -2031,4 +2032,3 @@ def test_get_invoice(self, get_invoice_mock):
20312032
owner = OwnerFactory()
20322033
self.billing_service.get_invoice(owner, "abc")
20332034
get_invoice_mock.assert_called_once_with(owner, "abc")
2034-

0 commit comments

Comments
 (0)