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

Commit ea2d934

Browse files
authored
fix: Account for Multiple Owners being returned when handling stripe payment intent webhook (#1146)
1 parent 4ed721d commit ea2d934

File tree

2 files changed

+151
-11
lines changed

2 files changed

+151
-11
lines changed

billing/tests/test_views.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,6 +1643,132 @@ class MockPaymentMethod:
16431643
"sub_123", default_payment_method=payment_method_retrieve_mock.return_value
16441644
)
16451645

1646+
@patch("logging.Logger.error")
1647+
@patch("services.billing.stripe.PaymentMethod.attach")
1648+
@patch("services.billing.stripe.Customer.modify")
1649+
@patch("services.billing.stripe.Subscription.modify")
1650+
@patch("services.billing.stripe.PaymentMethod.retrieve")
1651+
def test_check_and_handle_delayed_notification_payment_methods_no_subscription(
1652+
self,
1653+
payment_method_retrieve_mock,
1654+
subscription_modify_mock,
1655+
customer_modify_mock,
1656+
payment_method_attach_mock,
1657+
log_error_mock,
1658+
):
1659+
class MockPaymentMethod:
1660+
type = "us_bank_account"
1661+
us_bank_account = {}
1662+
id = "pm_123"
1663+
1664+
payment_method_retrieve_mock.return_value = MockPaymentMethod()
1665+
1666+
self.owner.stripe_subscription_id = None
1667+
self.owner.stripe_customer_id = "cus_123"
1668+
self.owner.save()
1669+
1670+
handler = StripeWebhookHandler()
1671+
handler._check_and_handle_delayed_notification_payment_methods(
1672+
"cus_123", "pm_123"
1673+
)
1674+
1675+
payment_method_retrieve_mock.assert_called_once_with("pm_123")
1676+
payment_method_attach_mock.assert_not_called()
1677+
customer_modify_mock.assert_not_called()
1678+
subscription_modify_mock.assert_not_called()
1679+
1680+
log_error_mock.assert_called_once_with(
1681+
"No owners found with that customer_id, something went wrong",
1682+
extra=dict(customer_id="cus_123"),
1683+
)
1684+
1685+
@patch("logging.Logger.error")
1686+
@patch("services.billing.stripe.PaymentMethod.attach")
1687+
@patch("services.billing.stripe.Customer.modify")
1688+
@patch("services.billing.stripe.Subscription.modify")
1689+
@patch("services.billing.stripe.PaymentMethod.retrieve")
1690+
def test_check_and_handle_delayed_notification_payment_methods_no_customer(
1691+
self,
1692+
payment_method_retrieve_mock,
1693+
subscription_modify_mock,
1694+
customer_modify_mock,
1695+
payment_method_attach_mock,
1696+
log_error_mock,
1697+
):
1698+
class MockPaymentMethod:
1699+
type = "us_bank_account"
1700+
us_bank_account = {}
1701+
id = "pm_123"
1702+
1703+
payment_method_retrieve_mock.return_value = MockPaymentMethod()
1704+
1705+
handler = StripeWebhookHandler()
1706+
handler._check_and_handle_delayed_notification_payment_methods(
1707+
"cus_1", "pm_123"
1708+
)
1709+
1710+
payment_method_retrieve_mock.assert_called_once_with("pm_123")
1711+
payment_method_attach_mock.assert_not_called()
1712+
customer_modify_mock.assert_not_called()
1713+
subscription_modify_mock.assert_not_called()
1714+
1715+
log_error_mock.assert_called_once_with(
1716+
"No owners found with that customer_id, something went wrong",
1717+
extra=dict(customer_id="cus_1"),
1718+
)
1719+
1720+
@patch("services.billing.stripe.PaymentMethod.attach")
1721+
@patch("services.billing.stripe.Customer.modify")
1722+
@patch("services.billing.stripe.Subscription.modify")
1723+
@patch("services.billing.stripe.PaymentMethod.retrieve")
1724+
def test_check_and_handle_delayed_notification_payment_methods_multiple_subscriptions(
1725+
self,
1726+
payment_method_retrieve_mock,
1727+
subscription_modify_mock,
1728+
customer_modify_mock,
1729+
payment_method_attach_mock,
1730+
):
1731+
class MockPaymentMethod:
1732+
type = "us_bank_account"
1733+
us_bank_account = {}
1734+
id = "pm_123"
1735+
1736+
payment_method_retrieve_mock.return_value = MockPaymentMethod()
1737+
1738+
self.owner.stripe_subscription_id = "sub_123"
1739+
self.owner.stripe_customer_id = "cus_123"
1740+
self.owner.save()
1741+
1742+
OwnerFactory(stripe_subscription_id="sub_124", stripe_customer_id="cus_123")
1743+
1744+
handler = StripeWebhookHandler()
1745+
handler._check_and_handle_delayed_notification_payment_methods(
1746+
"cus_123", "pm_123"
1747+
)
1748+
1749+
payment_method_retrieve_mock.assert_called_once_with("pm_123")
1750+
payment_method_attach_mock.assert_called_once_with(
1751+
payment_method_retrieve_mock.return_value, customer="cus_123"
1752+
)
1753+
customer_modify_mock.assert_called_once_with(
1754+
"cus_123",
1755+
invoice_settings={
1756+
"default_payment_method": payment_method_retrieve_mock.return_value
1757+
},
1758+
)
1759+
subscription_modify_mock.assert_has_calls(
1760+
[
1761+
call(
1762+
"sub_123",
1763+
default_payment_method=payment_method_retrieve_mock.return_value,
1764+
),
1765+
call(
1766+
"sub_124",
1767+
default_payment_method=payment_method_retrieve_mock.return_value,
1768+
),
1769+
]
1770+
)
1771+
16461772
@patch(
16471773
"billing.views.StripeWebhookHandler._check_and_handle_delayed_notification_payment_methods"
16481774
)

billing/views.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,6 @@ def _check_and_handle_delayed_notification_payment_methods(
539539
When verification succeeds, this attaches the payment method to the customer and sets
540540
it as the default payment method for both the customer and subscription.
541541
"""
542-
owner = Owner.objects.get(stripe_customer_id=customer_id)
543542
payment_method = stripe.PaymentMethod.retrieve(payment_method_id)
544543

545544
is_us_bank_account = payment_method.type == "us_bank_account" and hasattr(
@@ -548,19 +547,34 @@ def _check_and_handle_delayed_notification_payment_methods(
548547

549548
should_set_as_default = is_us_bank_account
550549

550+
# attach the payment method + set as default on the invoice and subscription
551551
if should_set_as_default:
552-
# attach the payment method + set as default on the invoice and subscription
553-
stripe.PaymentMethod.attach(
554-
payment_method, customer=owner.stripe_customer_id
555-
)
556-
stripe.Customer.modify(
557-
owner.stripe_customer_id,
558-
invoice_settings={"default_payment_method": payment_method},
559-
)
560-
stripe.Subscription.modify(
561-
owner.stripe_subscription_id, default_payment_method=payment_method
552+
# retrieve the number of owners to update
553+
owners = Owner.objects.filter(
554+
stripe_customer_id=customer_id, stripe_subscription_id__isnull=False
562555
)
563556

557+
if owners.exists():
558+
# Even if multiple results are returned, these two stripe calls are
559+
# just for a single customer
560+
stripe.PaymentMethod.attach(payment_method, customer=customer_id)
561+
stripe.Customer.modify(
562+
customer_id,
563+
invoice_settings={"default_payment_method": payment_method},
564+
)
565+
566+
# But this one is for each subscription an owner may have
567+
for owner in owners:
568+
stripe.Subscription.modify(
569+
owner.stripe_subscription_id,
570+
default_payment_method=payment_method,
571+
)
572+
else:
573+
log.error(
574+
"No owners found with that customer_id, something went wrong",
575+
extra=dict(customer_id=customer_id),
576+
)
577+
564578
def payment_intent_succeeded(self, payment_intent: stripe.PaymentIntent) -> None:
565579
"""
566580
Stripe payment_intent.succeeded webhook event is emitted when a

0 commit comments

Comments
 (0)