Skip to content

Commit

Permalink
Test remaining stripe views
Browse files Browse the repository at this point in the history
  • Loading branch information
rebkwok committed Jul 14, 2024
1 parent 58f2d90 commit 74b1e71
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 18 deletions.
12 changes: 12 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ def invoice(configured_user):
)


def get_mock_setup_intent(**params):
defaults = {
"object": "setup_intent",
"id": "mock-intent-id",
"status": "succeeded",
"currency": "gbp",
"client_secret": "secret",
}
options = {**defaults, **params}
return Mock(**options)


def get_mock_payment_intent(webhook_event_type=None, **params):
defaults = {
"object": "payment_intent",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ ACTION REQUIRED: CHECK STATUS OF ERROR TRANSACTION/EVENT
{% if payment_intent %}
Payment Error
-------------
Stripe payment intent id: {{ payment_intent.id }}
Stripe payment/setup intent id: {{ payment_intent.id }}
payment status: {{ payment_intent.status }}
{% endif %}
{% if event_type %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ <h4 class="card-title">Membership setup succeeded</h4>
{% if payment %}
<p class="card-text">
{% if updating %}
Thank you for updating up your membership. Your transaction for your next month's membership has been completed and you'll receive confirmation by email shortly.
Thank you for updating your membership. Your transaction for your next month's membership has been completed and you'll receive confirmation by email shortly.
{% else %}
Thank you for setting up your membership. Your transaction for your first month's membership has been completed and you'll receive confirmation by email shortly.
{% endif %}
</p>
{% elif setup %}
<p class="card-text">
{% if updating %}
Thank you for updating up your membership. Your payment for your next month's membership will be taken on the 25th of the month.
Thank you for updating your membership. Your payment for your next month's membership will be taken on the 25th of the month.
{% else %}
Thank you for setting up your membership. You'll receive confirmation by email shortly. Your first payment will be taken on the 25th of the month.
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion stripe_payments/tests/mock_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,4 @@ def customer_portal_configuration(self):

def customer_portal_url(self, customer_id):
self._record(self.customer_portal_url, [customer_id])
raise NotImplementedError
return f"https://example.com/portal/{customer_id}/"
108 changes: 107 additions & 1 deletion stripe_payments/tests/test_stripe_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import stripe
from model_bakery import baker

from conftest import get_mock_payment_intent
from conftest import get_mock_payment_intent, get_mock_setup_intent
from booking.models import Block, Booking, TicketBooking, Ticket
from ..models import Invoice, StripePaymentIntent
from .mock_connector import MockConnector
Expand Down Expand Up @@ -388,3 +388,109 @@ def test_return_with_processing_payment_intent(mock_payment_intent, client, conf
assert "Your payment is processing" in resp.content.decode("utf-8")
assert len(mail.outbox) == 1
assert mail.outbox[0].to == [settings.SUPPORT_EMAIL]


# stripe portal
@pytest.mark.usefixtures("seller")
@patch("stripe_payments.views.views.StripeConnector", MockConnector)
def test_stripe_portal_view(client):
resp = client.get(reverse("stripe_payments:stripe_portal", args=("customer-id-123",)))
assert resp.status_code == 302
assert resp.url == f"https://example.com/portal/customer-id-123/"


# stripe subscribe view (stripe return url for subscriptions)

subscribe_complete_url = reverse("stripe_payments:stripe_subscribe_complete")

@pytest.mark.parametrize(
"payment_intent_status,updating,success,template_match",
[
("succeeded", False, True, "Thank you for setting up your membership"),
("succeeded", True, True, "Thank you for updating your membership"),
("processing", True, False, "Your payment is processing"),
("unknown", False, False, "Error Processing Payment"),
]
)
@pytest.mark.usefixtures("seller")
@patch("stripe_payments.utils.stripe.PaymentIntent")
def test_stripe_subscribe_complete_with_payment_intent(mock_payment_intent, client, payment_intent_status, updating, success, template_match):
mock_payment_intent.retrieve.return_value = get_mock_payment_intent(status=payment_intent_status)
url = f"{subscribe_complete_url}?payment_intent=pi_123"
if updating:
url += "&updating=true"
resp = client.get(url)
assert resp.status_code == 200

assert template_match in resp.content.decode()
if success:
assert resp.context["updating"] == updating
assert resp.context["payment"]
assert len(mail.outbox) == 0
else:
assert len(mail.outbox) == 1
assert "Something went wrong processing a stripe event" in mail.outbox[0].subject


@pytest.mark.usefixtures("seller")
@patch("stripe_payments.utils.stripe.PaymentIntent")
def test_stripe_subscribe_complete_with_payment_intent_error(mock_payment_intent, client):
mock_payment_intent.retrieve.side_effect = stripe.InvalidRequestError("err", None)
url = f"{subscribe_complete_url}?payment_intent=pi_123"
resp = client.get(url)
assert resp.status_code == 200

assert "Error Processing Payment" in resp.content.decode()
assert len(mail.outbox) == 1
assert "Something went wrong processing a stripe event" in mail.outbox[0].subject


@pytest.mark.parametrize(
"setup_intent_status,updating,success,template_match",
[
("succeeded", False, True, "Thank you for setting up your membership"),
("succeeded", True, True, "Thank you for updating your membership"),
("processing", True, False, "Your payment is processing"),
("unknown", False, False, "Error Processing Payment"),
]
)
@pytest.mark.usefixtures("seller")
@patch("stripe_payments.utils.stripe.SetupIntent")
def test_stripe_subscribe_complete_with_setup_intent(mock_setup_intent, client, setup_intent_status, updating, success, template_match):
mock_setup_intent.retrieve.return_value = get_mock_setup_intent(status=setup_intent_status)
url = f"{subscribe_complete_url}?setup_intent=su_123"
if updating:
url += "&updating=true"
resp = client.get(url)
assert resp.status_code == 200
assert template_match in resp.content.decode()
if success:
assert resp.context["updating"] == updating
assert resp.context["setup"]
assert len(mail.outbox) == 0
else:
assert len(mail.outbox) == 1
assert "Something went wrong processing a stripe event" in mail.outbox[0].subject


@pytest.mark.usefixtures("seller")
@patch("stripe_payments.utils.stripe.SetupIntent")
def test_stripe_subscribe_complete_with_setup_intent_error(mock_setup_intent, client):
mock_setup_intent.retrieve.side_effect = stripe.InvalidRequestError("err", None)
url = f"{subscribe_complete_url}?setup_intent=su_123"
resp = client.get(url)
assert resp.status_code == 200

assert "Error Processing Payment" in resp.content.decode()
assert len(mail.outbox) == 1
assert "Something went wrong processing a stripe event" in mail.outbox[0].subject


@pytest.mark.usefixtures("seller")
def test_stripe_subscribe_complete_with_unknown_payment_type(client):
resp = client.get(subscribe_complete_url)
assert resp.status_code == 200

assert "Error Processing Payment" in resp.content.decode()
assert len(mail.outbox) == 1
assert "Something went wrong processing a stripe event" in mail.outbox[0].subject
74 changes: 70 additions & 4 deletions stripe_payments/tests/test_stripe_webhook_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,14 @@ def test_webhook_authorized_account_no_seller(
webhook_event_type="account.application.authorized"
)
mock_account.list.return_value = Mock(data=[Mock(id="stripe-account-1")])

Seller.objects.all().delete()
resp = client.post(webhook_url, data={}, HTTP_STRIPE_SIGNATURE="foo")
assert resp.status_code == 400
assert resp.status_code == 200
assert "Stripe account has no associated seller on this site" in resp.content.decode()


@patch("stripe_payments.views.webhook.stripe.Webhook")
def test_webhook_authorized_account_mismatched_seller(
def test_webhook_payment_intent_succeeded_mismatched_seller(
mock_webhook, get_mock_webhook_event, client, invoice
):
baker.make(Block, paid=True, block_type__cost=10, invoice=invoice)
Expand All @@ -264,7 +265,7 @@ def test_webhook_authorized_account_mismatched_seller(


@patch("stripe_payments.views.webhook.stripe.Webhook")
def test_webhook_authorized_account_no_seller(
def test_webhook_payment_intent_succeeded_no_seller(
mock_webhook, get_mock_webhook_event, client, invoice
):
baker.make(Block, paid=True, block_type__cost=10, invoice=invoice)
Expand Down Expand Up @@ -321,6 +322,30 @@ def test_webhook_subscription_created(
assert membership.user_memberships.first().start_date.date() == datetime(2024, 7, 1).date()


@patch("booking.models.membership_models.StripeConnector", MockConnector)
@patch("stripe_payments.views.webhook.stripe.Webhook")
def test_webhook_subscription_created_setup_pending(
mock_webhook, get_mock_webhook_event, client, configured_stripe_user
):
membership = baker.make(Membership, name="membership1")
assert not membership.user_memberships.exists()
mock_webhook.construct_event.return_value = get_mock_webhook_event(
webhook_event_type="customer.subscription.created",
start_date = datetime(2024, 6, 25).timestamp(),
pending_setup_intent="su_123",
status="active",
)
resp = client.post(webhook_url, data={}, HTTP_STRIPE_SIGNATURE="foo")
assert resp.status_code == 200
# no emails sent for initial creation with no default payment method (i.e. setup but not paid/confirmed yet)
assert len(mail.outbox) == 0
# membership created, with start date as first of next month
assert membership.user_memberships.count() == 1
user_membership = membership.user_memberships.first()
assert user_membership.start_date.date() == datetime(2024, 7, 1).date()
assert user_membership.subscription_status == "setup_pending"


@patch("booking.models.membership_models.StripeConnector", MockConnector)
@patch("stripe_payments.views.webhook.stripe.Webhook")
def test_webhook_setup_intent_succeeded_for_subscription_with_user_membership(
Expand Down Expand Up @@ -508,6 +533,34 @@ def test_webhook_subscription_updated_status_changed_to_active_from_incomplete(
assert paid_booking.membership is None



@pytest.mark.freeze_time("2024-02-26")
@patch("booking.models.membership_models.StripeConnector", MockConnector)
@patch("stripe_payments.views.webhook.stripe.Webhook")
def test_webhook_subscription_updated_status_changed_to_incomplete_expired(
mock_webhook, get_mock_webhook_event, client, configured_stripe_user
):
mock_webhook.construct_event.return_value = get_mock_webhook_event(
webhook_event_type="customer.subscription.updated"
)
# status incomplete, changed to incomplete_expired deletes UserMembership
membership = baker.make(Membership, name="membership1")
baker.make(
UserMembership, membership=membership, user=configured_stripe_user, subscription_id="id",
start_date=datetime(2024, 1, 25, tzinfo=datetime_tz.utc), subscription_status="incomplete"
)
assert membership.user_memberships.count() == 1
mock_webhook.construct_event.return_value = get_mock_webhook_event(
webhook_event_type="customer.subscription.updated",
status="incomplete_expired",
canceled_at=None,
)
resp = client.post(webhook_url, data={}, HTTP_STRIPE_SIGNATURE="foo")
assert resp.status_code == 200, resp.content

assert not membership.user_memberships.exists()


@patch("booking.models.membership_models.StripeConnector", MockConnector)
@patch("stripe_payments.views.webhook.stripe.Webhook")
def test_webhook_subscription_updated_status_scheduled_to_cancel(
Expand Down Expand Up @@ -753,3 +806,16 @@ def test_webhook_refund_updated_for_subscription(
assert resp.status_code == 200, resp.content
assert len(mail.outbox) == 1
assert mail.outbox[0].to == [settings.SUPPORT_EMAIL]


@patch("stripe_payments.views.webhook.get_invoice_from_event_metadata")
@patch("stripe_payments.views.webhook.stripe.Webhook")
def test_webhook_unexpected_exception(
mock_webhook, mock_get_invoice, get_mock_webhook_event, client
):
mock_webhook.construct_event.return_value = get_mock_webhook_event()
mock_get_invoice.side_effect = Exception("err")
resp = client.post(webhook_url, data={}, HTTP_STRIPE_SIGNATURE="foo")
assert resp.status_code == 400, resp.content
assert len(mail.outbox) == 1
assert mail.outbox[0].to == [settings.SUPPORT_EMAIL]
4 changes: 2 additions & 2 deletions stripe_payments/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ def update_stripe_customer(self, customer_id, **kwargs):
**kwargs
)

def get_payment_intent(self, setup_intent_id):
def get_payment_intent(self, payment_intent_id):
return stripe.PaymentIntent.retrieve(
id=setup_intent_id, stripe_account=self.connected_account_id
id=payment_intent_id, stripe_account=self.connected_account_id
)

def get_setup_intent(self, setup_intent_id):
Expand Down
18 changes: 11 additions & 7 deletions stripe_payments/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,22 @@ def stripe_payment_complete(request):

def stripe_subscribe_complete(request):
subscribe_type = None
intent_id = request.GET.get("payment_intent")
updating = request.GET.get("updating", False)
if intent_id:
updating = "updating" in request.GET

if "payment_intent" in request.GET:
intent_id = request.GET.get("payment_intent")
subscribe_type = "payment"
else:
elif "setup_intent" in request.GET:
intent_id = request.GET.get("setup_intent")
subscribe_type = "setup"

if subscribe_type is None:
error = f"Could not identify payment or setup intent for subscription"
logger.error(error)
send_failed_payment_emails(
payment_intent=None,
error=error
)
return render(request, 'stripe_payments/non_valid_payment.html')

client = StripeConnector(request)
Expand All @@ -94,7 +99,7 @@ def stripe_subscribe_complete(request):
# All confirmation emails are handled in the webhook
if subscribe_type == "payment":
try:
intent = stripe.PaymentIntent.retrieve(intent_id, stripe_account=client.connected_account_id)
intent = client.get_payment_intent(intent_id)
except stripe.error.InvalidRequestError as e:
error = f"Error retrieving Stripe payment intent: {e}"
logger.error(e)
Expand All @@ -106,7 +111,7 @@ def stripe_subscribe_complete(request):
else:
assert subscribe_type == "setup"
try:
intent = stripe.SetupIntent.retrieve(intent_id, stripe_account=client.connected_account_id)
intent = client.get_setup_intent(intent_id)
except stripe.error.InvalidRequestError as e:
error = f"Error retrieving Stripe setup intent: {e}"
logger.error(e)
Expand All @@ -132,7 +137,6 @@ def stripe_subscribe_complete(request):

assert subscribe_type == "setup"
if intent.status == "succeeded":
# _process_completed_stripe_subscription(intent, client.connected_account, subscribe_type=subscribe_type, request=request)
return render(request, 'stripe_payments/valid_subscription_setup.html', {"setup": True, "updating": updating})
elif intent.status == "processing":
error = f"Setup intent {intent.id} still processing."
Expand Down

0 comments on commit 74b1e71

Please sign in to comment.