def test_with_different_govuk_payment_status_updates_session( self, govuk_status, payment_url, requests_mock, ): """ Test that if the GOV.UK payment status is not the same as the payment gateway session one, the record is updated. """ # choose an initial status != from the govuk one to test the update initial_status = PaymentGatewaySessionStatus.created if initial_status == govuk_status: initial_status = PaymentGatewaySessionStatus.started session = PaymentGatewaySessionFactory(status=initial_status) # mock GOV.UK call used to get the existing session requests_mock.get( govuk_url(f'payments/{session.govuk_payment_id}'), status_code=200, json={ 'state': { 'status': govuk_status }, 'payment_id': session.govuk_payment_id, '_links': { 'next_url': None if not payment_url else { 'href': payment_url }, }, }, ) # make API call url = reverse( 'api-v3:omis-public:payment-gateway-session:detail', kwargs={ 'public_token': session.order.public_token, 'pk': session.id }, ) client = self.create_api_client( scope=Scope.public_omis_front_end, grant_type=Application.GRANT_CLIENT_CREDENTIALS, ) response = client.get(url) assert response.status_code == status.HTTP_200_OK # refresh record session.refresh_from_db() assert session.status == govuk_status # check API response assert response.json() == { 'id': str(session.id), 'created_on': format_date_or_datetime(session.created_on), 'status': govuk_status, 'payment_url': payment_url, }
def test_with_govuk_pay_erroring_when_refreshing(self, requests_mock): """ Test that if GOV.UK Pay cancels the payment but errors when refreshing the session, the session object is not updated (but the GOV.UK payment is still cancelled). This is okay as the session object will get refreshed at the next opportunity. """ session = PaymentGatewaySessionFactory() original_session_status = session.status requests_mock.post( govuk_url(f'payments/{session.govuk_payment_id}/cancel'), status_code=204, ) requests_mock.get( govuk_url(f'payments/{session.govuk_payment_id}'), status_code=500, ) with pytest.raises(GOVUKPayAPIException): session.cancel() session.refresh_from_db() assert session.status == original_session_status assert requests_mock.call_count == 2
def test_atomicity_when_order_save_errors(self, requests_mock): """ Test that if the order.mark_as_paid() call fails, non of the changes persists. """ session = PaymentGatewaySessionFactory() original_session_status = session.status url = govuk_url(f'payments/{session.govuk_payment_id}') requests_mock.get( url, status_code=200, json={ 'state': { 'status': 'success' }, }, ) session.order.mark_as_paid = mock.MagicMock(side_effect=Exception()) with pytest.raises(Exception): session.refresh_from_govuk_payment() session.refresh_from_db() assert session.status == original_session_status assert requests_mock.call_count == 1
def test_with_different_govuk_payment_status_updates_session( self, status, requests_mock): """ Test that if the GOV.UK payment status is not the same as the payment gateway session one, the record is updated. """ # choose an initial status != from the govuk one to test the update initial_status = PaymentGatewaySessionStatus.created if initial_status == status: initial_status = PaymentGatewaySessionStatus.started session = PaymentGatewaySessionFactory(status=initial_status) url = govuk_url(f'payments/{session.govuk_payment_id}') requests_mock.get( url, status_code=200, json={ 'state': { 'status': status }, }, ) assert session.refresh_from_govuk_payment() session.refresh_from_db() assert session.status == status assert requests_mock.call_count == 1
def test_atomicity_when_govuk_pay_errors(self, requests_mock): """ Test that if GOV.UK Pay errors, none of the changes persists. """ session = PaymentGatewaySessionFactory() original_session_status = session.status url = govuk_url(f'payments/{session.govuk_payment_id}') requests_mock.get(url, status_code=500) with pytest.raises(GOVUKPayAPIException): assert session.refresh_from_govuk_payment() session.refresh_from_db() assert session.status == original_session_status assert requests_mock.call_count == 1
def test_with_govuk_pay_erroring_when_cancelling(self, requests_mock): """ Test that if GOV.UK Pay errors when cancelling the payment, the session object is not updated. """ session = PaymentGatewaySessionFactory() original_session_status = session.status requests_mock.post( govuk_url(f'payments/{session.govuk_payment_id}/cancel'), status_code=500, ) with pytest.raises(GOVUKPayAPIException): session.cancel() session.refresh_from_db() assert session.status == original_session_status assert requests_mock.call_count == 1
def test_cancel_updates_session(self, requests_mock): """ Test that if GOV.UK Pay cancels and acknowledges the change, the session object is updated. """ session = PaymentGatewaySessionFactory() requests_mock.post( govuk_url(f'payments/{session.govuk_payment_id}/cancel'), status_code=204, ) requests_mock.get( govuk_url(f'payments/{session.govuk_payment_id}'), status_code=200, json={'state': {'status': 'cancelled'}}, ) session.cancel() session.refresh_from_db() assert session.status == PaymentGatewaySessionStatus.CANCELLED assert requests_mock.call_count == 2
def test_with_govuk_payment_success_updates_order(self, requests_mock): """ Test that if the GOV.UK payment status is `success` and the payment gateway session is out of date, the record is updated, the related order marked as `paid` and an OMIS `payment.Payment` record created from the GOV.UK response data one. """ order = OrderWithAcceptedQuoteFactory() session = PaymentGatewaySessionFactory( order=order, status=PaymentGatewaySessionStatus.created, ) # mock GOV.UK calls used to refresh the payment session # Pay says that the payment completed successfully requests_mock.get( govuk_url(f'payments/{session.govuk_payment_id}'), status_code=200, json={ 'amount': order.total_cost, 'state': { 'status': 'success' }, 'email': '*****@*****.**', 'reference': '12345', 'created_date': '2018-02-13T14:56:56.734Z', '_links': { 'next_url': None, }, 'card_details': { 'last_digits_card_number': '1111', 'cardholder_name': 'John Doe', 'expiry_date': '01/20', 'billing_address': { 'line1': 'line 1 address', 'line2': 'line 2 address', 'postcode': 'SW1A 1AA', 'city': 'London', 'country': 'GB', }, 'card_brand': 'Visa', }, }, ) # make API call url = reverse( 'api-v3:omis-public:payment-gateway-session:detail', kwargs={ 'public_token': order.public_token, 'pk': session.id }, ) client = self.create_api_client( scope=Scope.public_omis_front_end, grant_type=Application.GRANT_CLIENT_CREDENTIALS, ) response = client.get(url) assert response.status_code == status.HTTP_200_OK # check API response assert response.json() == { 'id': str(session.id), 'created_on': format_date_or_datetime(session.created_on), 'status': PaymentGatewaySessionStatus.success, 'payment_url': '', } # check session record session.refresh_from_db() assert session.status == PaymentGatewaySessionStatus.success # check order and payment order.refresh_from_db() assert order.status == OrderStatus.paid assert Payment.objects.filter(order=order).count() == 1
def test_409_if_refresh_updates_order_status_to_paid(self, requests_mock): """ Test that if the system is not up-to-date, the order is in quote_accepted but the GOV.UK payment happens, the endpoint triggers a check on existing sessions, realises that one finished successfully and records the payment marking the order as 'paid'. For this reason, the endpoint returns 409 - Conflict as no other payment can be made. """ # set up db order = OrderWithAcceptedQuoteFactory() existing_session = PaymentGatewaySessionFactory( order=order, status=PaymentGatewaySessionStatus.started, ) # mock GOV.UK requests used to refresh the payment session. # GOV.UK Pay says that the payment completed successfully requests_mock.get( govuk_url(f'payments/{existing_session.govuk_payment_id}'), status_code=200, json={ 'amount': order.total_cost, 'state': { 'status': 'success' }, 'email': '*****@*****.**', 'reference': '12345', 'created_date': '2018-02-13T14:56:56.734Z', 'card_details': { 'last_digits_card_number': '1111', 'cardholder_name': 'John Doe', 'expiry_date': '01/20', 'billing_address': { 'line1': 'line 1 address', 'line2': 'line 2 address', 'postcode': 'SW1A 1AA', 'city': 'London', 'country': 'GB', }, 'card_brand': 'Visa', }, }, ) # make API call url = reverse( 'api-v3:omis-public:payment-gateway-session:collection', kwargs={'public_token': order.public_token}, ) client = self.create_api_client( scope=Scope.public_omis_front_end, grant_type=Application.GRANT_CLIENT_CREDENTIALS, ) response = client.post(url) assert response.status_code == status.HTTP_409_CONFLICT # check session record existing_session.refresh_from_db() assert existing_session.status == PaymentGatewaySessionStatus.success # check order and pyament order.refresh_from_db() assert order.status == OrderStatus.paid assert Payment.objects.filter(order=order).count() == 1
def test_with_govuk_payment_success_updates_order(self, requests_mock): """ Test that if the GOV.UK payment status is `success` and the payment gateway session is out of date, the record is updated, the related order marked as `paid` and an OMIS `payment.Payment` record created from the GOV.UK response data one. """ order = OrderWithAcceptedQuoteFactory() session = PaymentGatewaySessionFactory( status=PaymentGatewaySessionStatus.created, order=order, ) url = govuk_url(f'payments/{session.govuk_payment_id}') response_json = { 'amount': order.total_cost, 'state': { 'status': 'success' }, 'email': '*****@*****.**', 'created_date': '2018-02-13T14:56:56.734Z', 'reference': '12345', 'card_details': { 'last_digits_card_number': '1111', 'cardholder_name': 'John Doe', 'expiry_date': '01/20', 'billing_address': { 'line1': 'line 1 address', 'line2': 'line 2 address', 'postcode': 'SW1A 1AA', 'city': 'London', 'country': 'GB', }, 'card_brand': 'Visa', }, } requests_mock.get(url, status_code=200, json=response_json) assert session.refresh_from_govuk_payment() # check session session.refresh_from_db() assert session.status == PaymentGatewaySessionStatus.success # check order order.refresh_from_db() assert order.status == OrderStatus.paid # check payment object assert Payment.objects.filter(order=order).count() == 1 payment = Payment.objects.filter(order=order).first() assert payment.amount == response_json['amount'] assert payment.method == PaymentMethod.card assert payment.received_on == dateutil_parse('2018-02-13').date() assert payment.transaction_reference == '12345' assert payment.cardholder_name == 'John Doe' assert payment.card_brand == 'Visa' assert payment.billing_email == '*****@*****.**' assert payment.billing_address_1 == 'line 1 address' assert payment.billing_address_2 == 'line 2 address' assert payment.billing_address_town == 'London' assert payment.billing_address_postcode == 'SW1A 1AA' assert payment.billing_address_country == 'GB' assert requests_mock.call_count == 1
def test_exception_if_refresh_updates_order_status_to_paid(self, requests_mock): """ Test that if the system is not up-to-date, the order is in quote_accepted but the GOV.UK payment happens, the method triggers a check on existing sessions, realises that one finished successfully and records the payment marking the order as 'paid'. For this reason, the method raises APIConflictException as no other payment can be started. """ # set up db order = OrderWithAcceptedQuoteFactory() existing_session = PaymentGatewaySessionFactory( order=order, status=PaymentGatewaySessionStatus.STARTED, ) # mock GOV.UK requests used to refresh the payment session, # GOV.UK Pay says that the payment completed successfully requests_mock.get( govuk_url(f'payments/{existing_session.govuk_payment_id}'), status_code=200, json={ 'amount': order.total_cost, 'state': {'status': 'success'}, 'email': '*****@*****.**', 'created_date': '2018-02-13T14:56:56.734Z', 'reference': '12345', 'card_details': { 'last_digits_card_number': '1111', 'cardholder_name': 'John Doe', 'expiry_date': '01/20', 'billing_address': { 'line1': 'line 1 address', 'line2': 'line 2 address', 'postcode': 'SW1A 1AA', 'city': 'London', 'country': 'GB', }, 'card_brand': 'Visa', }, }, ) with pytest.raises(APIConflictException): PaymentGatewaySession.objects.create_from_order(order) # check session record existing_session.refresh_from_db() assert existing_session.status == PaymentGatewaySessionStatus.SUCCESS # check order and payment order.refresh_from_db() assert order.status == OrderStatus.PAID assert Payment.objects.count() == 1 payment = Payment.objects.first() assert payment.amount == order.total_cost assert payment.method == PaymentMethod.CARD assert payment.received_on == dateutil_parse('2018-02-13').date() assert payment.transaction_reference == '12345' assert payment.cardholder_name == 'John Doe' assert payment.billing_address_1 == 'line 1 address' assert payment.billing_address_2 == 'line 2 address' assert payment.billing_address_town == 'London' assert payment.billing_address_postcode == 'SW1A 1AA' assert payment.billing_address_country == 'GB' assert payment.billing_email == '*****@*****.**' assert payment.card_brand == 'Visa'