Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

fix: Capture-context modification for existing Payment Intents #4154

Merged
merged 3 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/migrations-mysql8.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
pip uninstall -y mysqlclient
pip install --no-binary mysqlclient mysqlclient
pip uninstall -y xmlsec
pip install --no-binary xmlsec xmlsec
pip install --no-binary xmlsec xmlsec==1.3.13

- name: Initiate Services
run: |
Expand Down
11 changes: 10 additions & 1 deletion ecommerce/extensions/payment/processors/stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,16 @@ def get_capture_context(self, request):
if payment_intent_id:
stripe_response = stripe.PaymentIntent.retrieve(id=payment_intent_id)
status = stripe_response['status']
if status != 'requires_payment_method' or status != 'requires_confirmation':
logger.info(
'Stripe capture-context called for basket [%d] and order number [%s] with '
'existing Payment Intent [%s] with status [%s]',
basket.id,
basket.order_number,
payment_intent_id,
status,
)
confirmable_statuses = ['requires_payment_method', 'requires_confirmation']
if status not in confirmable_statuses:
# Payment Intent is in a non-confirmable status, must create a new one
stripe_response = self.cancel_and_create_new_payment_intent_for_basket(basket, payment_intent_id)
# If a Payment Intent exists in a confirmable status, it will skip the below else statement,
Expand Down
33 changes: 33 additions & 0 deletions ecommerce/extensions/payment/tests/views/test_stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,39 @@ def test_capture_context_large_characters_basket(self):
# The metadata must be less than 500 characters
assert len(mock_create.call_args.kwargs['metadata']['courses']) < 500

@file_data('fixtures/test_stripe_test_payment_flow.json')
def test_capture_context_confirmable_status(
self,
confirm_resp, # pylint: disable=unused-argument
confirm_resp_in_progress, # pylint: disable=unused-argument
create_resp, # pylint: disable=unused-argument
modify_resp, # pylint: disable=unused-argument
cancel_resp, # pylint: disable=unused-argument
refund_resp, # pylint: disable=unused-argument
retrieve_addr_resp,
retrieve_resp_in_progress): # pylint: disable=unused-argument
"""
Verify that hitting capture-context with a Payment Intent that already exists and it's in a status that
can be confirmed, that a new Payment Intent is not created for this basket.
"""
basket = self.create_basket(product_class=SEAT_PRODUCT_CLASS_NAME)
payment_intent_id = retrieve_addr_resp['id']
basket_add_payment_intent_id_attribute(basket, payment_intent_id)

with mock.patch('stripe.PaymentIntent.retrieve') as mock_retrieve:
mock_retrieve.return_value = retrieve_addr_resp
# If Payment Intent already exists for this basket, and it's in a usable status that
# can later be confirmed, make sure we do not cancel and create a new Payment Intent.
with mock.patch('stripe.PaymentIntent.cancel') as mock_cancel:
self.client.get(self.capture_context_url)
mock_cancel.assert_not_called()
payment_intent_id = BasketAttribute.objects.get(
basket=basket,
attribute_type__name=PAYMENT_INTENT_ID_ATTRIBUTE
).value_text
assert payment_intent_id == mock_retrieve.return_value['id']
assert retrieve_addr_resp['status'] == 'requires_confirmation'

@file_data('fixtures/test_stripe_test_payment_flow.json')
def test_capture_context_in_progress_payment(
self,
Expand Down
Loading