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

Commit 4073b1e

Browse files
incorporate pr comments
1 parent bb3eb24 commit 4073b1e

File tree

2 files changed

+175
-38
lines changed

2 files changed

+175
-38
lines changed

services/billing.py

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ def create_setup_intent(self, owner: Owner) -> stripe.SetupIntent:
725725
)
726726

727727
@_log_stripe_error
728-
def get_unverified_payment_methods(self, owner):
728+
def get_unverified_payment_methods(self, owner: Owner):
729729
log.info(
730730
"Getting unverified payment methods",
731731
extra=dict(
@@ -738,42 +738,58 @@ def get_unverified_payment_methods(self, owner):
738738
unverified_payment_methods = []
739739

740740
# Check payment intents
741-
payment_intents = stripe.PaymentIntent.list(
742-
customer=owner.stripe_customer_id, limit=100
743-
)
744-
for intent in payment_intents.data or []:
745-
if (
746-
intent.get("next_action")
747-
and intent.next_action
748-
and intent.next_action.get("type") == "verify_with_microdeposits"
749-
):
750-
unverified_payment_methods.extend(
751-
[
752-
{
753-
"payment_method_id": intent.payment_method,
754-
"hosted_verification_url": intent.next_action.verify_with_microdeposits.hosted_verification_url,
755-
}
756-
]
757-
)
741+
has_more = True
742+
starting_after = None
743+
while has_more:
744+
payment_intents = stripe.PaymentIntent.list(
745+
customer=owner.stripe_customer_id,
746+
limit=20,
747+
starting_after=starting_after
748+
)
749+
for intent in payment_intents.data or []:
750+
if (
751+
intent.get("next_action")
752+
and intent.next_action
753+
and intent.next_action.get("type") == "verify_with_microdeposits"
754+
):
755+
unverified_payment_methods.extend(
756+
[
757+
{
758+
"payment_method_id": intent.payment_method,
759+
"hosted_verification_url": intent.next_action.verify_with_microdeposits.hosted_verification_url,
760+
}
761+
]
762+
)
763+
has_more = payment_intents.has_more
764+
if has_more and payment_intents.data:
765+
starting_after = payment_intents.data[-1].id
758766

759767
# Check setup intents
760-
setup_intents = stripe.SetupIntent.list(
761-
customer=owner.stripe_customer_id, limit=100
762-
)
763-
for intent in setup_intents.data:
764-
if (
765-
intent.get("next_action")
766-
and intent.next_action
767-
and intent.next_action.get("type") == "verify_with_microdeposits"
768-
):
769-
unverified_payment_methods.extend(
770-
[
771-
{
772-
"payment_method_id": intent.payment_method,
773-
"hosted_verification_url": intent.next_action.verify_with_microdeposits.hosted_verification_url,
774-
}
775-
]
776-
)
768+
has_more = True
769+
starting_after = None
770+
while has_more:
771+
setup_intents = stripe.SetupIntent.list(
772+
customer=owner.stripe_customer_id,
773+
limit=20,
774+
starting_after=starting_after
775+
)
776+
for intent in setup_intents.data:
777+
if (
778+
intent.get("next_action")
779+
and intent.next_action
780+
and intent.next_action.get("type") == "verify_with_microdeposits"
781+
):
782+
unverified_payment_methods.extend(
783+
[
784+
{
785+
"payment_method_id": intent.payment_method,
786+
"hosted_verification_url": intent.next_action.verify_with_microdeposits.hosted_verification_url,
787+
}
788+
]
789+
)
790+
has_more = setup_intents.has_more
791+
if has_more and setup_intents.data:
792+
starting_after = setup_intents.data[-1].id
777793

778794
return unverified_payment_methods
779795

@@ -817,7 +833,7 @@ def apply_cancellation_discount(self, owner: Owner):
817833
def create_setup_intent(self, owner):
818834
pass
819835

820-
def get_unverified_payment_methods(self, owner):
836+
def get_unverified_payment_methods(self, owner: Owner):
821837
pass
822838

823839

@@ -850,7 +866,7 @@ def get_invoice(self, owner, invoice_id):
850866
def list_filtered_invoices(self, owner, limit=10):
851867
return self.payment_service.list_filtered_invoices(owner, limit)
852868

853-
def get_unverified_payment_methods(self, owner):
869+
def get_unverified_payment_methods(self, owner: Owner):
854870
return self.payment_service.get_unverified_payment_methods(owner)
855871

856872
def update_plan(self, owner, desired_plan):

services/tests/test_billing.py

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from unittest.mock import MagicMock, patch
2+
from unittest.mock import MagicMock, call, patch
33

44
import requests
55
from django.conf import settings
@@ -1847,6 +1847,7 @@ def test_get_unverified_payment_methods(
18471847
owner = OwnerFactory(stripe_customer_id="test-customer-id")
18481848
payment_intent = PaymentIntent.construct_from(
18491849
{
1850+
"id": "pi_123",
18501851
"payment_method": "pm_123",
18511852
"next_action": {
18521853
"type": "verify_with_microdeposits",
@@ -1860,6 +1861,7 @@ def test_get_unverified_payment_methods(
18601861

18611862
setup_intent = SetupIntent.construct_from(
18621863
{
1864+
"id": "si_123",
18631865
"payment_method": "pm_456",
18641866
"next_action": {
18651867
"type": "verify_with_microdeposits",
@@ -1872,7 +1874,9 @@ def test_get_unverified_payment_methods(
18721874
)
18731875

18741876
payment_intent_list_mock.return_value.data = [payment_intent]
1877+
payment_intent_list_mock.return_value.has_more = False
18751878
setup_intent_list_mock.return_value.data = [setup_intent]
1879+
setup_intent_list_mock.return_value.has_more = False
18761880

18771881
expected = [
18781882
{
@@ -1886,6 +1890,123 @@ def test_get_unverified_payment_methods(
18861890
]
18871891
assert self.stripe.get_unverified_payment_methods(owner) == expected
18881892

1893+
@patch("services.billing.stripe.PaymentIntent.list")
1894+
@patch("services.billing.stripe.SetupIntent.list")
1895+
def test_get_unverified_payment_methods_pagination(
1896+
self, setup_intent_list_mock, payment_intent_list_mock
1897+
):
1898+
owner = OwnerFactory(stripe_customer_id="test-customer-id")
1899+
1900+
# Create 42 payment intents with only 2 having microdeposits verification
1901+
payment_intents = []
1902+
for i in range(42):
1903+
next_action = None
1904+
if i in [0, 41]: # First and last have verification
1905+
next_action = {
1906+
"type": "verify_with_microdeposits",
1907+
"verify_with_microdeposits": {
1908+
"hosted_verification_url": f"https://verify.stripe.com/pi_{i}"
1909+
},
1910+
}
1911+
payment_intents.append(
1912+
PaymentIntent.construct_from(
1913+
{
1914+
"id": f"pi_{i}",
1915+
"payment_method": f"pm_pi_{i}",
1916+
"next_action": next_action,
1917+
},
1918+
"fake_api_key",
1919+
)
1920+
)
1921+
1922+
# Create 42 setup intents with only 2 having microdeposits verification
1923+
setup_intents = []
1924+
for i in range(42):
1925+
next_action = None
1926+
if i in [0, 41]: # First and last have verification
1927+
next_action = {
1928+
"type": "verify_with_microdeposits",
1929+
"verify_with_microdeposits": {
1930+
"hosted_verification_url": f"https://verify.stripe.com/si_{i}"
1931+
},
1932+
}
1933+
setup_intents.append(
1934+
SetupIntent.construct_from(
1935+
{
1936+
"id": f"si_{i}",
1937+
"payment_method": f"pm_si_{i}",
1938+
"next_action": next_action,
1939+
},
1940+
"fake_api_key",
1941+
)
1942+
)
1943+
1944+
# Split into pages of 20
1945+
payment_intent_pages = [
1946+
type(
1947+
"obj",
1948+
(object,),
1949+
{
1950+
"data": payment_intents[i : i + 20],
1951+
"has_more": i + 20 < len(payment_intents),
1952+
},
1953+
)
1954+
for i in range(0, len(payment_intents), 20)
1955+
]
1956+
1957+
setup_intent_pages = [
1958+
type(
1959+
"obj",
1960+
(object,),
1961+
{
1962+
"data": setup_intents[i : i + 20],
1963+
"has_more": i + 20 < len(setup_intents),
1964+
},
1965+
)
1966+
for i in range(0, len(setup_intents), 20)
1967+
]
1968+
1969+
payment_intent_list_mock.side_effect = payment_intent_pages
1970+
setup_intent_list_mock.side_effect = setup_intent_pages
1971+
1972+
expected = [
1973+
{
1974+
"payment_method_id": "pm_pi_0",
1975+
"hosted_verification_url": "https://verify.stripe.com/pi_0",
1976+
},
1977+
{
1978+
"payment_method_id": "pm_pi_41",
1979+
"hosted_verification_url": "https://verify.stripe.com/pi_41",
1980+
},
1981+
{
1982+
"payment_method_id": "pm_si_0",
1983+
"hosted_verification_url": "https://verify.stripe.com/si_0",
1984+
},
1985+
{
1986+
"payment_method_id": "pm_si_41",
1987+
"hosted_verification_url": "https://verify.stripe.com/si_41",
1988+
},
1989+
]
1990+
1991+
result = self.stripe.get_unverified_payment_methods(owner)
1992+
assert result == expected
1993+
assert len(result) == 4 # Verify we got exactly 4 results
1994+
1995+
# Verify pagination calls
1996+
payment_intent_calls = [
1997+
call(customer="test-customer-id", limit=20, starting_after=None),
1998+
call(customer="test-customer-id", limit=20, starting_after="pi_19"),
1999+
call(customer="test-customer-id", limit=20, starting_after="pi_39"),
2000+
]
2001+
setup_intent_calls = [
2002+
call(customer="test-customer-id", limit=20, starting_after=None),
2003+
call(customer="test-customer-id", limit=20, starting_after="si_19"),
2004+
call(customer="test-customer-id", limit=20, starting_after="si_39"),
2005+
]
2006+
2007+
payment_intent_list_mock.assert_has_calls(payment_intent_calls)
2008+
setup_intent_list_mock.assert_has_calls(setup_intent_calls)
2009+
18892010

18902011
class MockPaymentService(AbstractPaymentService):
18912012
def list_filtered_invoices(self, owner, limit=10):

0 commit comments

Comments
 (0)