Skip to content

Commit

Permalink
incorporate pr comments
Browse files Browse the repository at this point in the history
  • Loading branch information
suejung-sentry committed Jan 30, 2025
1 parent bb3eb24 commit 4073b1e
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 38 deletions.
90 changes: 53 additions & 37 deletions services/billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ def create_setup_intent(self, owner: Owner) -> stripe.SetupIntent:
)

@_log_stripe_error
def get_unverified_payment_methods(self, owner):
def get_unverified_payment_methods(self, owner: Owner):
log.info(
"Getting unverified payment methods",
extra=dict(
Expand All @@ -738,42 +738,58 @@ def get_unverified_payment_methods(self, owner):
unverified_payment_methods = []

# Check payment intents
payment_intents = stripe.PaymentIntent.list(
customer=owner.stripe_customer_id, limit=100
)
for intent in payment_intents.data or []:
if (
intent.get("next_action")
and intent.next_action
and intent.next_action.get("type") == "verify_with_microdeposits"
):
unverified_payment_methods.extend(
[
{
"payment_method_id": intent.payment_method,
"hosted_verification_url": intent.next_action.verify_with_microdeposits.hosted_verification_url,
}
]
)
has_more = True
starting_after = None
while has_more:
payment_intents = stripe.PaymentIntent.list(
customer=owner.stripe_customer_id,
limit=20,
starting_after=starting_after
)
for intent in payment_intents.data or []:
if (
intent.get("next_action")
and intent.next_action
and intent.next_action.get("type") == "verify_with_microdeposits"
):
unverified_payment_methods.extend(
[
{
"payment_method_id": intent.payment_method,
"hosted_verification_url": intent.next_action.verify_with_microdeposits.hosted_verification_url,
}
]
)
has_more = payment_intents.has_more
if has_more and payment_intents.data:
starting_after = payment_intents.data[-1].id

# Check setup intents
setup_intents = stripe.SetupIntent.list(
customer=owner.stripe_customer_id, limit=100
)
for intent in setup_intents.data:
if (
intent.get("next_action")
and intent.next_action
and intent.next_action.get("type") == "verify_with_microdeposits"
):
unverified_payment_methods.extend(
[
{
"payment_method_id": intent.payment_method,
"hosted_verification_url": intent.next_action.verify_with_microdeposits.hosted_verification_url,
}
]
)
has_more = True
starting_after = None
while has_more:
setup_intents = stripe.SetupIntent.list(
customer=owner.stripe_customer_id,
limit=20,
starting_after=starting_after
)
for intent in setup_intents.data:
if (
intent.get("next_action")
and intent.next_action
and intent.next_action.get("type") == "verify_with_microdeposits"
):
unverified_payment_methods.extend(
[
{
"payment_method_id": intent.payment_method,
"hosted_verification_url": intent.next_action.verify_with_microdeposits.hosted_verification_url,
}
]
)
has_more = setup_intents.has_more
if has_more and setup_intents.data:
starting_after = setup_intents.data[-1].id

return unverified_payment_methods

Expand Down Expand Up @@ -817,7 +833,7 @@ def apply_cancellation_discount(self, owner: Owner):
def create_setup_intent(self, owner):
pass

def get_unverified_payment_methods(self, owner):
def get_unverified_payment_methods(self, owner: Owner):
pass


Expand Down Expand Up @@ -850,7 +866,7 @@ def get_invoice(self, owner, invoice_id):
def list_filtered_invoices(self, owner, limit=10):
return self.payment_service.list_filtered_invoices(owner, limit)

def get_unverified_payment_methods(self, owner):
def get_unverified_payment_methods(self, owner: Owner):
return self.payment_service.get_unverified_payment_methods(owner)

def update_plan(self, owner, desired_plan):
Expand Down
123 changes: 122 additions & 1 deletion services/tests/test_billing.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock, call, patch

import requests
from django.conf import settings
Expand Down Expand Up @@ -1847,6 +1847,7 @@ def test_get_unverified_payment_methods(
owner = OwnerFactory(stripe_customer_id="test-customer-id")
payment_intent = PaymentIntent.construct_from(
{
"id": "pi_123",
"payment_method": "pm_123",
"next_action": {
"type": "verify_with_microdeposits",
Expand All @@ -1860,6 +1861,7 @@ def test_get_unverified_payment_methods(

setup_intent = SetupIntent.construct_from(
{
"id": "si_123",
"payment_method": "pm_456",
"next_action": {
"type": "verify_with_microdeposits",
Expand All @@ -1872,7 +1874,9 @@ def test_get_unverified_payment_methods(
)

payment_intent_list_mock.return_value.data = [payment_intent]
payment_intent_list_mock.return_value.has_more = False
setup_intent_list_mock.return_value.data = [setup_intent]
setup_intent_list_mock.return_value.has_more = False

expected = [
{
Expand All @@ -1886,6 +1890,123 @@ def test_get_unverified_payment_methods(
]
assert self.stripe.get_unverified_payment_methods(owner) == expected

@patch("services.billing.stripe.PaymentIntent.list")
@patch("services.billing.stripe.SetupIntent.list")
def test_get_unverified_payment_methods_pagination(
self, setup_intent_list_mock, payment_intent_list_mock
):
owner = OwnerFactory(stripe_customer_id="test-customer-id")

# Create 42 payment intents with only 2 having microdeposits verification
payment_intents = []
for i in range(42):
next_action = None
if i in [0, 41]: # First and last have verification
next_action = {
"type": "verify_with_microdeposits",
"verify_with_microdeposits": {
"hosted_verification_url": f"https://verify.stripe.com/pi_{i}"
},
}
payment_intents.append(
PaymentIntent.construct_from(
{
"id": f"pi_{i}",
"payment_method": f"pm_pi_{i}",
"next_action": next_action,
},
"fake_api_key",
)
)

# Create 42 setup intents with only 2 having microdeposits verification
setup_intents = []
for i in range(42):
next_action = None
if i in [0, 41]: # First and last have verification
next_action = {
"type": "verify_with_microdeposits",
"verify_with_microdeposits": {
"hosted_verification_url": f"https://verify.stripe.com/si_{i}"
},
}
setup_intents.append(
SetupIntent.construct_from(
{
"id": f"si_{i}",
"payment_method": f"pm_si_{i}",
"next_action": next_action,
},
"fake_api_key",
)
)

# Split into pages of 20
payment_intent_pages = [
type(
"obj",
(object,),
{
"data": payment_intents[i : i + 20],
"has_more": i + 20 < len(payment_intents),
},
)
for i in range(0, len(payment_intents), 20)
]

setup_intent_pages = [
type(
"obj",
(object,),
{
"data": setup_intents[i : i + 20],
"has_more": i + 20 < len(setup_intents),
},
)
for i in range(0, len(setup_intents), 20)
]

payment_intent_list_mock.side_effect = payment_intent_pages
setup_intent_list_mock.side_effect = setup_intent_pages

expected = [
{
"payment_method_id": "pm_pi_0",
"hosted_verification_url": "https://verify.stripe.com/pi_0",
},
{
"payment_method_id": "pm_pi_41",
"hosted_verification_url": "https://verify.stripe.com/pi_41",
},
{
"payment_method_id": "pm_si_0",
"hosted_verification_url": "https://verify.stripe.com/si_0",
},
{
"payment_method_id": "pm_si_41",
"hosted_verification_url": "https://verify.stripe.com/si_41",
},
]

result = self.stripe.get_unverified_payment_methods(owner)
assert result == expected
assert len(result) == 4 # Verify we got exactly 4 results

# Verify pagination calls
payment_intent_calls = [
call(customer="test-customer-id", limit=20, starting_after=None),
call(customer="test-customer-id", limit=20, starting_after="pi_19"),
call(customer="test-customer-id", limit=20, starting_after="pi_39"),
]
setup_intent_calls = [
call(customer="test-customer-id", limit=20, starting_after=None),
call(customer="test-customer-id", limit=20, starting_after="si_19"),
call(customer="test-customer-id", limit=20, starting_after="si_39"),
]

payment_intent_list_mock.assert_has_calls(payment_intent_calls)
setup_intent_list_mock.assert_has_calls(setup_intent_calls)


class MockPaymentService(AbstractPaymentService):
def list_filtered_invoices(self, owner, limit=10):
Expand Down

0 comments on commit 4073b1e

Please sign in to comment.