Skip to content

Commit

Permalink
feat: Add Update Billing Address service function (#611)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajay-sentry authored Jun 12, 2024
1 parent ff7cbca commit c5b388b
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 0 deletions.
11 changes: 11 additions & 0 deletions api/internal/owner/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ def update_email(self, request, *args, **kwargs):
billing.update_email_address(owner, new_email)
return Response(self.get_serializer(owner).data)

@action(detail=False, methods=["patch"])
@stripe_safe
def update_billing_address(self, request, *args, **kwargs):
billing_address = request.data.get("billing_address")
if not billing_address:
raise ValidationError(detail="No billing_address sent")
owner = self.get_object()
billing = BillingService(requesting_user=request.current_owner)
billing.update_billing_address(owner, billing_address)
return Response(self.get_serializer(owner).data)


class UsersOrderingFilter(filters.OrderingFilter):
def get_valid_fields(self, queryset, view, context=None):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Stripe-Version:
- '2024-04-10'
User-Agent:
- Stripe/v1 PythonBindings/9.6.0
X-Stripe-Client-User-Agent:
- '{"bindings_version": "9.6.0", "lang": "python", "publisher": "stripe", "httplib":
"requests", "lang_version": "3.12.3", "platform": "Linux-6.6.31-linuxkit-aarch64-with-glibc2.36",
"uname": "Linux b0efe3849169 6.6.31-linuxkit #1 SMP Thu May 23 08:36:57 UTC
2024 aarch64 "}'
method: GET
uri: https://api.stripe.com/v1/subscriptions/djfos?expand%5B0%5D=latest_invoice&expand%5B1%5D=customer&expand%5B2%5D=customer.invoice_settings.default_payment_method
response:
body:
string: "{\n \"error\": {\n \"code\": \"resource_missing\",\n \"doc_url\":
\"https://stripe.com/docs/error-codes/resource-missing\",\n \"message\":
\"No such subscription: 'djfos'\",\n \"param\": \"id\",\n \"request_log_url\":
\"https://dashboard.stripe.com/test/logs/req_4POjCjRMnbE5Wg?t=1718056644\",\n
\ \"type\": \"invalid_request_error\"\n }\n}\n"
headers:
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Origin:
- '*'
Access-Control-Expose-Headers:
- Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required,
X-Stripe-Privileged-Session-Required
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Connection:
- keep-alive
Content-Length:
- '324'
Content-Security-Policy:
- report-uri https://q.stripe.com/csp-report?p=v1%2Fsubscriptions%2F%3Asubscription_exposed_id;
block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action
'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample';
style-src 'self'
Content-Type:
- application/json
Cross-Origin-Opener-Policy-Report-Only:
- same-origin; report-to="coop"
Date:
- Mon, 10 Jun 2024 21:57:24 GMT
Report-To:
- '{"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=billing-api-srv"}],"include_subdomains":true}'
Reporting-Endpoints:
- coop="https://q.stripe.com/coop-report?s=billing-api-srv"
Request-Id:
- req_4POjCjRMnbE5Wg
Server:
- nginx
Strict-Transport-Security:
- max-age=63072000; includeSubDomains; preload
Stripe-Version:
- '2024-04-10'
Vary:
- Origin
X-Content-Type-Options:
- nosniff
X-Stripe-Routing-Context-Priority-Tier:
- api-testmode
status:
code: 404
message: Not Found
version: 1
64 changes: 64 additions & 0 deletions api/internal/tests/views/test_account_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,70 @@ def test_update_email_address(self, modify_customer_mock, retrieve_mock):
self.current_owner.stripe_customer_id, email=new_email
)

def test_update_billing_address_without_body(self):
kwargs = {
"service": self.current_owner.service,
"owner_username": self.current_owner.username,
}
url = reverse("account_details-update-billing-address", kwargs=kwargs)
response = self.client.patch(url, format="json")
assert response.status_code == status.HTTP_400_BAD_REQUEST

@patch("services.billing.StripeService.update_billing_address")
def test_update_billing_address_handles_stripe_error(self, stripe_mock):
code, message = 402, "Oops, nope"
self.current_owner.stripe_customer_id = "flsoe"
self.current_owner.stripe_subscription_id = "djfos"
self.current_owner.save()

stripe_mock.side_effect = StripeError(message=message, http_status=code)

billing_address = {
"line_1": "45 Fremont St.",
"line_2": "",
"city": "San Francisco",
"state": "CA",
"country": "US",
"postal_code": "94105",
}
kwargs = {
"service": self.current_owner.service,
"owner_username": self.current_owner.username,
}
data = {"billing_address": billing_address}
url = reverse("account_details-update-billing-address", kwargs=kwargs)
response = self.client.patch(url, data=data, format="json")
assert response.status_code == code
assert response.data["detail"] == message

@patch("services.billing.stripe.Subscription.retrieve")
@patch("services.billing.stripe.Customer.modify")
def test_update_billing_address(self, modify_customer_mock, retrieve_mock):
self.current_owner.stripe_customer_id = "flsoe"
self.current_owner.stripe_subscription_id = "djfos"
self.current_owner.save()

billing_address = {
"line_1": "45 Fremont St.",
"line_2": "",
"city": "San Francisco",
"state": "CA",
"country": "US",
"postal_code": "94105",
}
kwargs = {
"service": self.current_owner.service,
"owner_username": self.current_owner.username,
}
data = {"billing_address": billing_address}
url = reverse("account_details-update-billing-address", kwargs=kwargs)
response = self.client.patch(url, data=data, format="json")
assert response.status_code == status.HTTP_200_OK

modify_customer_mock.assert_called_once_with(
self.current_owner.stripe_customer_id, address=billing_address
)

@patch("api.shared.permissions.get_provider")
def test_update_without_admin_permissions_returns_404(self, get_provider_mock):
get_provider_mock.return_value = GetAdminProviderAdapter()
Expand Down
24 changes: 24 additions & 0 deletions services/billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,19 @@ def update_email_address(self, owner: Owner, email_address: str):
f"Stripe successfully updated email address for owner {owner.ownerid} by user #{self.requesting_user.ownerid}"
)

@_log_stripe_error
def update_billing_address(self, owner: Owner, billing_address):
log.info(f"Stripe update billing address for owner {owner.ownerid}")
if owner.stripe_subscription_id is None:
log.info(
f"stripe_subscription_id is None, cannot update billing address for owner {owner.ownerid}"
)
return None
stripe.Customer.modify(owner.stripe_customer_id, address=billing_address)
log.info(
f"Stripe successfully updated billing address for owner {owner.ownerid} by user #{self.requesting_user.ownerid}"
)

@_log_stripe_error
def apply_cancellation_discount(self, owner: Owner):
if owner.stripe_subscription_id is None:
Expand Down Expand Up @@ -494,6 +507,9 @@ def update_payment_method(self, owner, payment_method):
def update_email_address(self, owner, email_address):
pass

def update_billing_address(self, owner, billing_address):
pass

def get_schedule(self, owner):
pass

Expand Down Expand Up @@ -570,5 +586,13 @@ def update_email_address(self, owner: Owner, email_address: str):
"""
return self.payment_service.update_email_address(owner, email_address)

def update_billing_address(self, owner: Owner, billing_address):
"""
Takes an owner and a billing address. Try to update the owner's billing address
to the address passed in. Address should be validated via stripe component prior
to hitting this service method. Return None if invalid.
"""
return self.payment_service.update_billing_address(owner, billing_address)

def apply_cancellation_discount(self, owner: Owner):
return self.payment_service.apply_cancellation_discount(owner)
44 changes: 44 additions & 0 deletions services/tests/test_billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,50 @@ def test_update_email_address(self, modify_customer_mock):
self.stripe.update_email_address(owner, "[email protected]")
modify_customer_mock.assert_called_once_with(customer_id, email=email)

def test_update_billing_address_with_invalid_email(self):
owner = OwnerFactory(stripe_subscription_id=None)
assert self.stripe.update_billing_address(owner, "gabagool") == None

def test_update_billing_address_when_no_subscription(self):
owner = OwnerFactory(stripe_subscription_id=None)
assert (
self.stripe.update_billing_address(
owner,
billing_address={
"line_1": "45 Fremont St.",
"line_2": "",
"city": "San Francisco",
"state": "CA",
"country": "US",
"postal_code": "94105",
},
)
== None
)

@patch("services.billing.stripe.Customer.modify")
def test_update_billing_address(self, modify_customer_mock):
subscription_id = "sub_abc"
customer_id = "cus_abc"
owner = OwnerFactory(
stripe_subscription_id=subscription_id, stripe_customer_id=customer_id
)
billing_address = {
"line_1": "45 Fremont St.",
"line_2": "",
"city": "San Francisco",
"state": "CA",
"country": "US",
"postal_code": "94105",
}
self.stripe.update_billing_address(
owner,
billing_address=billing_address,
)
modify_customer_mock.assert_called_once_with(
customer_id, address=billing_address
)

@patch("services.billing.stripe.Invoice.retrieve")
def test_get_invoice_not_found(self, retrieve_invoice_mock):
invoice_id = "abc"
Expand Down

0 comments on commit c5b388b

Please sign in to comment.