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

Commit 0cf1a48

Browse files
incorporate pr comments
1 parent 33054ff commit 0cf1a48

File tree

3 files changed

+56
-71
lines changed

3 files changed

+56
-71
lines changed

billing/views.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def invoice_payment_failed(self, invoice: stripe.Invoice) -> None:
9292
if invoice.payment_intent:
9393
payment_intent = stripe.PaymentIntent.retrieve(invoice.payment_intent)
9494
if (
95-
payment_intent is not None
95+
payment_intent
9696
and payment_intent.get("status") == "requires_action"
9797
and payment_intent.get("next_action", {}).get("type")
9898
== "verify_with_microdeposits"
@@ -129,12 +129,12 @@ def invoice_payment_failed(self, invoice: stripe.Invoice) -> None:
129129
payment_intent = stripe.PaymentIntent.retrieve(
130130
invoice["payment_intent"], expand=["payment_method"]
131131
)
132-
card = (
133-
payment_intent.get("payment_method", {}).get("card")
134-
if payment_intent.get("payment_method")
135-
and not isinstance(payment_intent.get("payment_method"), str)
136-
else None
137-
)
132+
133+
try:
134+
card = payment_intent.payment_method.card
135+
except AttributeError:
136+
card = None
137+
138138
template_vars = {
139139
"amount": invoice.total / 100,
140140
"card_type": card.brand if card else None,
@@ -374,10 +374,10 @@ def _has_unverified_initial_payment_method(
374374
latest_invoice.payment_intent
375375
)
376376
return (
377-
payment_intent is not None
378-
and payment_intent.status == "requires_action"
379-
and payment_intent.next_action is not None
380-
and payment_intent.next_action.get("type")
377+
payment_intent
378+
and payment_intent.get("status") == "requires_action"
379+
and payment_intent.get("next_action")
380+
and payment_intent.get("next_action", {}).get("type")
381381
== "verify_with_microdeposits"
382382
)
383383
return False

services/billing.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -556,19 +556,22 @@ def _is_unverified_payment_method(self, payment_method_id: str) -> bool:
556556
setup_intents = stripe.SetupIntent.list(
557557
payment_method=payment_method_id, limit=1
558558
)
559-
if (
560-
setup_intents
561-
and hasattr(setup_intents, "data")
562-
and isinstance(setup_intents.data, list)
563-
and len(setup_intents.data) > 0
564-
):
559+
560+
try:
565561
latest_intent = setup_intents.data[0]
566562
if (
567563
latest_intent.status == "requires_action"
568564
and latest_intent.next_action
569565
and latest_intent.next_action.type == "verify_with_microdeposits"
570566
):
571567
return True
568+
except Exception as e:
569+
log.error(
570+
"Error retrieving latest setup intent",
571+
payment_method_id=payment_method_id,
572+
extra=dict(error=e),
573+
)
574+
return False
572575

573576
return False
574577

@@ -987,21 +990,23 @@ def create_setup_intent(self, owner: Owner):
987990
def _cleanup_incomplete_subscription(
988991
self, subscription: stripe.Subscription, owner: Owner
989992
):
990-
latest_invoice = subscription.get("latest_invoice")
991-
if not latest_invoice:
992-
return None
993-
payment_intent_id = latest_invoice.get("payment_intent")
994-
if not payment_intent_id:
993+
try:
994+
payment_intent_id = subscription.latest_invoice.payment_intent
995+
except Exception as e:
996+
log.error(
997+
"Latest invoice is missing payment intent id",
998+
extra=dict(error=e),
999+
)
9951000
return None
9961001

9971002
payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
9981003
if payment_intent.status == "requires_action":
9991004
log.info(
10001005
"Subscription has pending payment verification",
10011006
extra=dict(
1002-
subscription_id=subscription.id,
1003-
payment_intent_id=payment_intent.id,
1004-
payment_intent_status=payment_intent.status,
1007+
subscription_id=subscription.get("id"),
1008+
payment_intent_id=payment_intent.get("id"),
1009+
payment_intent_status=payment_intent.get("status"),
10051010
),
10061011
)
10071012
try:
@@ -1011,16 +1016,16 @@ def _cleanup_incomplete_subscription(
10111016
log.info(
10121017
"Deleted incomplete subscription",
10131018
extra=dict(
1014-
subscription_id=subscription.id,
1015-
payment_intent_id=payment_intent.id,
1019+
subscription_id=subscription.get("id"),
1020+
payment_intent_id=payment_intent.get("id"),
10161021
),
10171022
)
10181023
except Exception as e:
10191024
log.error(
10201025
"Failed to delete subscription",
10211026
extra=dict(
1022-
subscription_id=subscription.id,
1023-
payment_intent_id=payment_intent.id,
1027+
subscription_id=subscription.get("id"),
1028+
payment_intent_id=payment_intent.get("id"),
10241029
error=str(e),
10251030
),
10261031
)

services/tests/test_billing.py

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2239,11 +2239,9 @@ def test_update_plan_cleans_up_incomplete_subscription_and_creates_new_checkout(
22392239
owner = OwnerFactory(stripe_subscription_id="sub_123")
22402240
desired_plan = {"value": PlanName.CODECOV_PRO_YEARLY.value, "quantity": 10}
22412241

2242-
class MockSubscription:
2243-
def __init__(self):
2244-
self.status = "incomplete"
2245-
2246-
subscription = MockSubscription()
2242+
subscription = stripe.Subscription.construct_from(
2243+
{"status": "incomplete"}, "fake_api_key"
2244+
)
22472245
get_subscription_mock.return_value = subscription
22482246

22492247
self.billing_service.update_plan(owner, desired_plan)
@@ -2257,21 +2255,14 @@ def __init__(self):
22572255
def test_cleanup_incomplete_subscription(self, delete_mock, retrieve_mock):
22582256
owner = OwnerFactory(stripe_subscription_id="sub_123")
22592257

2260-
class MockSubscription:
2261-
id = "sub_123"
2262-
2263-
def get(self, key):
2264-
if key == "latest_invoice":
2265-
return {"payment_intent": "pi_123"}
2266-
return None
2267-
2268-
subscription = MockSubscription()
2269-
2270-
class MockPaymentIntent:
2271-
id = "pi_123"
2272-
status = "requires_action"
2273-
2274-
retrieve_mock.return_value = MockPaymentIntent()
2258+
payment_intent = stripe.PaymentIntent.construct_from(
2259+
{"id": "pi_123", "status": "requires_action"}, "fake_api_key"
2260+
)
2261+
subscription = stripe.Subscription.construct_from(
2262+
{"id": "abcd", "latest_invoice": {"payment_intent": "pi_123"}},
2263+
"fake_api_key",
2264+
)
2265+
retrieve_mock.return_value = payment_intent
22752266

22762267
self.billing_service._cleanup_incomplete_subscription(subscription, owner)
22772268

@@ -2285,13 +2276,9 @@ def test_cleanup_incomplete_subscription_no_latest_invoice(
22852276
):
22862277
owner = OwnerFactory(stripe_subscription_id="sub_123")
22872278

2288-
class MockSubscription:
2289-
id = "sub_123"
2290-
2291-
def get(self, key):
2292-
return None
2293-
2294-
subscription = MockSubscription()
2279+
subscription = stripe.Subscription.construct_from(
2280+
{"id": "sub_123"}, "fake_api_key"
2281+
)
22952282

22962283
result = self.billing_service._cleanup_incomplete_subscription(
22972284
subscription, owner
@@ -2335,21 +2322,14 @@ def test_cleanup_incomplete_subscription_delete_fails(
23352322
):
23362323
owner = OwnerFactory(stripe_subscription_id="sub_123")
23372324

2338-
class MockSubscription:
2339-
id = "sub_123"
2340-
2341-
def get(self, key):
2342-
if key == "latest_invoice":
2343-
return {"payment_intent": "pi_123"}
2344-
return None
2345-
2346-
subscription = MockSubscription()
2347-
2348-
class MockPaymentIntent:
2349-
id = "pi_123"
2350-
status = "requires_action"
2351-
2352-
retrieve_mock.return_value = MockPaymentIntent()
2325+
payment_intent = stripe.PaymentIntent.construct_from(
2326+
{"id": "pi_123", "status": "requires_action"}, "fake_api_key"
2327+
)
2328+
subscription = stripe.Subscription.construct_from(
2329+
{"id": "abcd", "latest_invoice": {"payment_intent": "pi_123"}},
2330+
"fake_api_key",
2331+
)
2332+
retrieve_mock.return_value = payment_intent
23532333
delete_mock.side_effect = Exception("Delete failed")
23542334

23552335
result = self.billing_service._cleanup_incomplete_subscription(

0 commit comments

Comments
 (0)