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

Commit d114f46

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

File tree

7 files changed

+146
-29
lines changed

7 files changed

+146
-29
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):
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
Stripe-Version:
12+
- 2024-12-18.acacia
13+
User-Agent:
14+
- Stripe/v1 PythonBindings/11.4.1
15+
X-Stripe-Client-User-Agent:
16+
- '{"bindings_version": "11.4.1", "lang": "python", "publisher": "stripe", "httplib":
17+
"requests", "lang_version": "3.12.8", "platform": "Linux-6.10.14-linuxkit-aarch64-with-glibc2.36",
18+
"uname": "Linux d20f7f8cacbc 6.10.14-linuxkit #1 SMP Fri Nov 29 17:22:03 UTC
19+
2024 aarch64 "}'
20+
method: GET
21+
uri: https://api.stripe.com/v1/customers/flsoe
22+
response:
23+
body:
24+
string: "{\n \"error\": {\n \"code\": \"resource_missing\",\n \"doc_url\":
25+
\"https://stripe.com/docs/error-codes/resource-missing\",\n \"message\":
26+
\"No such customer: 'flsoe'\",\n \"param\": \"id\",\n \"request_log_url\":
27+
\"https://dashboard.stripe.com/test/logs/req_6pLtLMgWfIwlrz?t=1736823224\",\n
28+
\ \"type\": \"invalid_request_error\"\n }\n}\n"
29+
headers:
30+
Access-Control-Allow-Credentials:
31+
- 'true'
32+
Access-Control-Allow-Methods:
33+
- GET, HEAD, PUT, PATCH, POST, DELETE
34+
Access-Control-Allow-Origin:
35+
- '*'
36+
Access-Control-Expose-Headers:
37+
- Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required,
38+
X-Stripe-Privileged-Session-Required
39+
Access-Control-Max-Age:
40+
- '300'
41+
Cache-Control:
42+
- no-cache, no-store
43+
Connection:
44+
- keep-alive
45+
Content-Length:
46+
- '320'
47+
Content-Security-Policy:
48+
- base-uri 'none'; default-src 'none'; form-action 'none'; frame-ancestors 'none';
49+
img-src 'self'; script-src 'self' 'report-sample'; style-src 'self'; upgrade-insecure-requests;
50+
report-uri https://q.stripe.com/csp-violation?q=MUUgmXSyxSreBM7-bAyGzVR-Wca5tyHBXs7Sj4nrUFvKjUmrsdjHKQ4xq7a_xXp1ovkYdGrNWQ%3D%3D
51+
Content-Type:
52+
- application/json
53+
Cross-Origin-Opener-Policy-Report-Only:
54+
- same-origin; report-to="coop"
55+
Date:
56+
- Tue, 14 Jan 2025 02:53:44 GMT
57+
Report-To:
58+
- '{"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report"}],"include_subdomains":true}'
59+
Reporting-Endpoints:
60+
- coop="https://q.stripe.com/coop-report"
61+
Request-Id:
62+
- req_6pLtLMgWfIwlrz
63+
Server:
64+
- nginx
65+
Strict-Transport-Security:
66+
- max-age=63072000; includeSubDomains; preload
67+
Stripe-Version:
68+
- 2024-12-18.acacia
69+
Vary:
70+
- Origin
71+
X-Content-Type-Options:
72+
- nosniff
73+
X-Stripe-Priority-Routing-Enabled:
74+
- 'true'
75+
X-Stripe-Routing-Context-Priority-Tier:
76+
- api-testmode
77+
X-Wc:
78+
- AB
79+
status:
80+
code: 404
81+
message: Not Found
82+
version: 1

api/internal/tests/views/test_account_viewset.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import os
33
from datetime import datetime
4-
from unittest.mock import MagicMock, patch
4+
from unittest.mock import MagicMock, Mock, patch
55

66
import pytest
77
from django.test import override_settings
@@ -1228,6 +1228,43 @@ 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, customer_retrieve_mock, payment_method_mock, modify_customer_mock, retrieve_mock
1237+
):
1238+
self.current_owner.stripe_customer_id = "flsoe"
1239+
self.current_owner.stripe_subscription_id = "djfos"
1240+
self.current_owner.save()
1241+
1242+
payment_method_id = "pm_123"
1243+
customer_retrieve_mock.return_value = {
1244+
"invoice_settings": {
1245+
"default_payment_method": payment_method_id
1246+
}
1247+
}
1248+
1249+
new_email = "[email protected]"
1250+
kwargs = {
1251+
"service": self.current_owner.service,
1252+
"owner_username": self.current_owner.username,
1253+
}
1254+
data = {"new_email": new_email, "should_propagate_to_payment_methods": True}
1255+
url = reverse("account_details-update-email", kwargs=kwargs)
1256+
response = self.client.patch(url, data=data, format="json")
1257+
assert response.status_code == status.HTTP_200_OK
1258+
1259+
modify_customer_mock.assert_called_once_with(
1260+
self.current_owner.stripe_customer_id, email=new_email
1261+
)
1262+
customer_retrieve_mock.assert_called_once_with(self.current_owner.stripe_customer_id)
1263+
payment_method_mock.assert_called_once_with(
1264+
payment_method_id,
1265+
billing_details={"email": new_email}
1266+
)
1267+
12311268
def test_update_billing_address_without_body(self):
12321269
kwargs = {
12331270
"service": self.current_owner.service,

graphql_api/tests/mutation/test_create_stripe_setup_intent.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,29 @@ def test_when_unauthenticated(self):
2525
data = self.gql_request(query, variables={"input": {"owner": "somename"}})
2626
assert data["createStripeSetupIntent"]["error"]["__typename"] == "UnauthenticatedError"
2727

28+
def test_when_unauthorized(self):
29+
other_owner = OwnerFactory(username="other-user")
30+
data = self.gql_request(
31+
query, owner=self.owner, variables={"input": {"owner": other_owner.username}}
32+
)
33+
assert data["createStripeSetupIntent"]["error"]["__typename"] == "UnauthorizedError"
34+
35+
@patch("services.billing.stripe.SetupIntent.create")
36+
def test_when_validation_error(self, setup_intent_create_mock):
37+
setup_intent_create_mock.side_effect = Exception("Some error")
38+
data = self.gql_request(
39+
query, owner=self.owner, variables={"input": {"owner": self.owner.username}}
40+
)
41+
assert data["createStripeSetupIntent"]["error"]["__typename"] == "ValidationError"
42+
43+
def test_when_owner_not_found(self):
44+
data = self.gql_request(
45+
query, owner=self.owner, variables={"input": {"owner": "nonexistent-user"}}
46+
)
47+
assert data["createStripeSetupIntent"]["error"]["__typename"] == "ValidationError"
48+
2849
@patch("services.billing.stripe.SetupIntent.create")
29-
def test_when_authenticated(self, setup_intent_create_mock):
50+
def test_success(self, setup_intent_create_mock):
3051
setup_intent_create_mock.return_value = {"client_secret": "test-client-secret"}
3152
data = self.gql_request(
3253
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ def update_email_address(self, owner: Owner, email_address: str, should_propagat
603603
try:
604604
default_payment_method = stripe.Customer.retrieve(
605605
owner.stripe_customer_id
606-
).invoice_settings.default_payment_method
606+
)["invoice_settings"]["default_payment_method"]
607607

608608
stripe.PaymentMethod.modify(
609609
default_payment_method,
@@ -701,7 +701,7 @@ def create_setup_intent(self, owner: Owner) -> stripe.SetupIntent:
701701
),
702702
)
703703
return stripe.SetupIntent.create(
704-
payment_method_types=['card', 'us_bank_account'],
704+
payment_method_configuration=settings.STRIPE_PAYMENT_METHOD_CONFIGURATION_ID,
705705
customer=owner.stripe_customer_id,
706706
)
707707

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)