diff --git a/ecommerce/extensions/payment/processors/stripe.py b/ecommerce/extensions/payment/processors/stripe.py index 43c17e44693..e54f6f38d33 100644 --- a/ecommerce/extensions/payment/processors/stripe.py +++ b/ecommerce/extensions/payment/processors/stripe.py @@ -275,8 +275,8 @@ def get_transaction_parameters(self, basket, request=None, use_client_side_check def handle_processor_response(self, response, basket=None): # pretty sure we should simply return/error if basket is None, as not # sure what it would mean if there - payment_intent_id = response['payment_intent_id'] - dynamic_payment_methods_enabled = response['dynamic_payment_methods_enabled'] + payment_intent_id = response.get('payment_intent_id', None) + dynamic_payment_methods_enabled = response.get('dynamic_payment_methods_enabled', None) == 'true' # NOTE: In the future we may want to get/create a Customer. See https://stripe.com/docs/api#customers. # rewrite order amount so it's updated for coupon & quantity and unchanged by the user diff --git a/ecommerce/extensions/payment/tests/processors/test_stripe.py b/ecommerce/extensions/payment/tests/processors/test_stripe.py index db59286997e..59ced2c5067 100644 --- a/ecommerce/extensions/payment/tests/processors/test_stripe.py +++ b/ecommerce/extensions/payment/tests/processors/test_stripe.py @@ -1,5 +1,6 @@ +import json import logging import mock @@ -17,93 +18,61 @@ BillingAddress = get_model('order', 'BillingAddress') Country = get_model('address', 'Country') +STRIPE_TEST_FIXTURE_PATH = 'ecommerce/extensions/payment/tests/views/fixtures/test_stripe_test_payment_flow.json' + class StripeTests(PaymentProcessorTestCaseMixin, TestCase): processor_class = Stripe processor_name = 'stripe' + def _get_response_data(self, response_type): + with open(STRIPE_TEST_FIXTURE_PATH, 'r') as fixtures: # pylint: disable=unspecified-encoding + return json.load(fixtures)['happy_path'][response_type] + def test_get_transaction_parameters(self): transaction_params = self.processor.get_transaction_parameters(self.basket) assert 'payment_page_url' in transaction_params.keys() - # TODO: update or remove these tests def test_handle_processor_response(self): - assert True - # payment_intent_1 = stripe.PaymentIntent.construct_from({ - # 'id': 'pi_testtesttest', - # 'source': { - # 'brand': 'visa', - # 'last4': '4242', - # }, - # }, 'fake-key') - - # payment_intent_2 = stripe.PaymentIntent.construct_from({ - # 'id': 'pi_testtesttest', - # 'source': { - # 'brand': 'visa', - # 'last4': '4242', - # }, - # 'status': 'succeeded', - # "charges": { - # "object": "list", - # "data": [ - # { - # "id": "ch_testtesttest", - # "object": "charge", - # "status": "succeeded", - # "payment_method_details": { - # "card": { - # "brand": "visa", - # "country": "US", - # "exp_month": 5, - # "exp_year": 2020, - # "fingerprint": "Xt5EWLLDS7FJjR1c", - # "funding": "credit", - # "last4": "4242", - # "network": "visa", - # }, - # "type": "card" - # }, - # } - # ] - # } - # }, 'fake-key') - - # with mock.patch('stripe.PaymentIntent.modify') as payment_intent_modify_mock: - # with mock.patch('stripe.PaymentIntent.confirm') as payment_intent_confirm_mock: - # payment_intent_modify_mock.return_value = payment_intent_1 - # payment_intent_confirm_mock.return_value = payment_intent_2 - # actual = self.processor.handle_processor_response(payment_intent_1, self.basket) - - # assert actual.transaction_id == payment_intent_1.id - # assert actual.total == self.basket.total_incl_tax - # assert actual.currency == self.basket.currency - - # self.assert_processor_response_recorded( - # self.processor_name, - # payment_intent_2.id, - # payment_intent_2, - # basket=self.basket - # ) - - # def test_handle_processor_response_error(self): - # payment_intent_1 = stripe.PaymentIntent.construct_from({ - # 'id': 'pi_testtesttest', - # 'source': { - # 'brand': 'visa', - # 'last4': '4242', - # }, - # }, 'fake-key') - # with mock.patch('stripe.PaymentIntent.modify') as charge_mock: - # charge_mock.side_effect = stripe.error.CardError( - # 'fake-msg', 'fake-param', 'fake-code', http_body='fake-body', http_status=500 - # ) - # self.assertRaises( - # TransactionDeclined, - # self.processor.handle_processor_response, - # payment_intent_1, - # self.basket - # ) + with mock.patch('stripe.PaymentIntent.modify') as mock_modify: + mock_modify.return_value = self._get_response_data('modify_resp') + with mock.patch('stripe.PaymentIntent.confirm') as mock_confirm: + confirm_response_data = self._get_response_data('confirm_resp') + mock_confirm.return_value = confirm_response_data + response = { + 'payment_intent_id': confirm_response_data['id'], + 'skus': self.basket.lines.first().stockrecord.partner_sku, + 'dynamic_payment_methods_enabled': 'false' + } + actual = self.processor.handle_processor_response(response, self.basket) + + assert actual.transaction_id == confirm_response_data['id'] + assert actual.total == self.basket.total_incl_tax + assert actual.currency == self.basket.currency + + self.assert_processor_response_recorded( + self.processor_name, + confirm_response_data['id'], + confirm_response_data, + basket=self.basket + ) + + def test_handle_processor_response_error(self): + with self.assertLogs(level='ERROR') as logger: + with mock.patch('stripe.PaymentIntent.modify') as mock_modify: + mock_modify.return_value = self._get_response_data('modify_resp') + with mock.patch('stripe.PaymentIntent.confirm') as mock_confirm: + mock_confirm.side_effect = stripe.error.CardError( + 'fake-msg', 'fake-param', 'fake-code', http_body='fake-body', http_status=500 + ) + confirm_response_data = self._get_response_data('confirm_resp') + self.assertRaises( + stripe.error.CardError, + self.processor.handle_processor_response, + confirm_response_data, + self.basket + ) + self.assertIn('Card Error for basket [{}]'.format(self.basket.id), logger.output[0]) def test_issue_credit(self): charge_reference_number = '9436' diff --git a/ecommerce/extensions/payment/tests/views/test_stripe.py b/ecommerce/extensions/payment/tests/views/test_stripe.py index baba553ddee..fc44256e5d0 100644 --- a/ecommerce/extensions/payment/tests/views/test_stripe.py +++ b/ecommerce/extensions/payment/tests/views/test_stripe.py @@ -172,7 +172,7 @@ def test_payment_flow( """ Verify that the stripe payment flow, hitting capture-context and stripe-checkout urls, results in a basket associated with the correct - stripe payment_intent_id. + stripe payment_intent_id, and a processor response is recorded. Args: confirm_resp: Response for confirm call on payment purchase @@ -211,9 +211,9 @@ def test_payment_flow( self.client.post( self.stripe_checkout_url, data={ - 'payment_intent_id': 'pi_3LsftNIadiFyUl1x2TWxaADZ', + 'payment_intent_id': create_resp['id'], 'skus': basket.lines.first().stockrecord.partner_sku, - 'dynamic_payment_methods_enabled': False, + 'dynamic_payment_methods_enabled': 'false', }, ) assert mock_retrieve.call_count == 1 @@ -235,8 +235,14 @@ def test_payment_flow( pprs = PaymentProcessorResponse.objects.filter( transaction_id="pi_3LsftNIadiFyUl1x2TWxaADZ" ) - # created when andle_processor_response is successful + # created when handle_processor_response is successful assert pprs.count() == 1 + self.assert_processor_response_recorded( + Stripe.NAME, + confirm_resp['id'], + confirm_resp, + basket=basket + ) def test_capture_context_basket_price_change(self): """ @@ -468,7 +474,7 @@ def test_payment_error_no_basket(self): data={ 'payment_intent_id': 'pi_3LsftNIadiFyUl1x2TWxaADZ', 'skus': '', - 'dynamic_payment_methods_enabled': False, + 'dynamic_payment_methods_enabled': 'false', }, ) assert response.status_code == 302 @@ -486,7 +492,7 @@ def test_payment_error_sku_mismatch(self): { 'payment_intent_id': 'pi_3LsftNIadiFyUl1x2TWxaADZ', 'skus': 'totally_the_wrong_sku', - 'dynamic_payment_methods_enabled': False, + 'dynamic_payment_methods_enabled': 'false', }, ) assert response.json() == {'sku_error': True} @@ -513,7 +519,7 @@ def test_payment_check_sdn_returns_hits(self): { 'payment_intent_id': 'pi_3LsftNIadiFyUl1x2TWxaADZ', 'skus': basket.lines.first().stockrecord.partner_sku, - 'dynamic_payment_methods_enabled': False, + 'dynamic_payment_methods_enabled': 'false', }, ) assert response.status_code == 400 @@ -531,7 +537,7 @@ def test_payment_handle_payment_intent_in_progress(self): { 'payment_intent_id': 'pi_3LsftNIadiFyUl1x2TWxaADZ', 'skus': basket.lines.first().stockrecord.partner_sku, - 'dynamic_payment_methods_enabled': True, + 'dynamic_payment_methods_enabled': 'true', }, in_progress_payment=True, ) @@ -552,7 +558,7 @@ def test_handle_payment_fails_with_carderror(self): { 'payment_intent_id': 'pi_3LsftNIadiFyUl1x2TWxaADZ', 'skus': basket.lines.first().stockrecord.partner_sku, - 'dynamic_payment_methods_enabled': False, + 'dynamic_payment_methods_enabled': 'false', }, confirm_side_effect=stripe.error.CardError('Oops!', {}, 'card_declined'), ) @@ -573,7 +579,7 @@ def test_handle_payment_fails_with_unexpected_error(self): { 'payment_intent_id': 'pi_3LsftNIadiFyUl1x2TWxaADZ', 'skus': basket.lines.first().stockrecord.partner_sku, - 'dynamic_payment_methods_enabled': False, + 'dynamic_payment_methods_enabled': 'false', }, ) assert response.status_code == 400