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

feat: Fix capture_context error on Payment MFE #3965

Merged
merged 6 commits into from
May 16, 2023
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
73 changes: 40 additions & 33 deletions ecommerce/extensions/payment/processors/stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,39 +111,46 @@ def get_capture_context(self, request):
basket.id,
basket.order_number,
)
return None
try:
stripe_response = stripe.PaymentIntent.create(
**self._build_payment_intent_parameters(basket),
# This means this payment intent can only be confirmed with secret key (as in, from ecommerce)
secret_key_confirmation='required',
# don't create a new intent for the same basket
idempotency_key=self.generate_basket_pi_idempotency_key(basket),
)
# id is the payment_intent_id from Stripe
transaction_id = stripe_response['id']

basket_add_payment_intent_id_attribute(basket, transaction_id)
# for when basket was already created, but with different amount
except stripe.error.IdempotencyError:
# if this PI has been created before, we should be able to retrieve
# it from Stripe using the payment_intent_id BasketAttribute.
# Note that we update the PI's price in handle_processor_response
# before hitting the confirm endpoint, so we don't need to do that here
payment_intent_id_attribute = BasketAttributeType.objects.get(name=PAYMENT_INTENT_ID_ATTRIBUTE)
payment_intent_attr = BasketAttribute.objects.get(
basket=basket,
attribute_type=payment_intent_id_attribute
)
transaction_id = payment_intent_attr.value_text.strip()
logger.info(
'Idempotency Error: Retrieving existing Payment Intent for basket [%d]'
' with transaction ID [%s] and order number [%s].',
basket.id,
transaction_id,
basket.order_number,
)
stripe_response = stripe.PaymentIntent.retrieve(id=transaction_id)
# Create a default stripe_response object with the necessary fields to combat 400 errors
stripe_response = {
'id': '',
'client_secret': '',
}
else:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason for else statement here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, seeing as we are no longer returning 'None' when it is an empty basket, the else stops from running the rest of the code under the try that would be for a basket with an item in it. So the else is to separate the logic between an empty and a populated basket, so that there is only one return of the capture_context later on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect!

try:
logger.info("*** GETTING STRIPE RESPONSE ***")
stripe_response = stripe.PaymentIntent.create(
**self._build_payment_intent_parameters(basket),
# This means this payment intent can only be confirmed with secret key (as in, from ecommerce)
secret_key_confirmation='required',
# don't create a new intent for the same basket
idempotency_key=self.generate_basket_pi_idempotency_key(basket),
)
logger.info("*** STRIPE RESPONSE %s ***", stripe_response)
# id is the payment_intent_id from Stripe
transaction_id = stripe_response['id']

basket_add_payment_intent_id_attribute(basket, transaction_id)
# for when basket was already created, but with different amount
except stripe.error.IdempotencyError:
# if this PI has been created before, we should be able to retrieve
# it from Stripe using the payment_intent_id BasketAttribute.
# Note that we update the PI's price in handle_processor_response
# before hitting the confirm endpoint, so we don't need to do that here
payment_intent_id_attribute = BasketAttributeType.objects.get(name=PAYMENT_INTENT_ID_ATTRIBUTE)
payment_intent_attr = BasketAttribute.objects.get(
basket=basket,
attribute_type=payment_intent_id_attribute
)
transaction_id = payment_intent_attr.value_text.strip()
logger.info(
'Idempotency Error: Retrieving existing Payment Intent for basket [%d]'
' with transaction ID [%s] and order number [%s].',
basket.id,
transaction_id,
basket.order_number,
)
stripe_response = stripe.PaymentIntent.retrieve(id=transaction_id)

new_capture_context = {
'key_id': stripe_response['client_secret'],
Expand Down
15 changes: 13 additions & 2 deletions ecommerce/extensions/payment/tests/views/test_stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,22 @@ def test_capture_context_empty_basket(self):
basket.flush()

with mock.patch('stripe.PaymentIntent.create') as mock_create:
mock_create.return_value = {
'id': '',
'client_secret': '',
}

self.assertTrue(basket.is_empty)
response = self.client.get(self.capture_context_url)

mock_create.assert_not_called()
self.assertDictEqual(response.json(), {})
self.assertEqual(response.status_code, 400)
self.assertDictEqual(response.json(), {
'capture_context': {
'key_id': mock_create.return_value['client_secret'],
'order_id': basket.order_number,
}
})
self.assertEqual(response.status_code, 200)

def test_payment_error_no_basket(self):
"""
Expand Down