def test_confirmation_redirects_for_completed_payments(self): self.choose_debit_card_payment_method() self.fill_in_prisoner_details() self.fill_in_amount() with responses.RequestsMock() as rsps: ref = 'wargle-blargle' processor_id = '3' mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/%s/' % ref), json={ 'processor_id': processor_id, 'recipient_name': 'John', 'amount': 1700, 'status': 'taken', 'created': datetime.datetime.now().isoformat() + 'Z', }, status=200, ) with self.patch_prisoner_details_check(), self.silence_logger(): response = self.client.get( self.url, {'payment_ref': ref}, follow=False ) self.assertRedirects(response, '/')
def test_update_incomplete_payments_no_govuk_payment_found(self): """ Test that if GOV.UK Pay returns 404 for one payment, the command marks the related MTP payment as failed. """ with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/'), json={ 'count': 1, 'results': [PAYMENT_DATA], }, status=200, ) rsps.add( rsps.GET, govuk_url(f'/payments/{PAYMENT_DATA["processor_id"]}/'), status=404, ) rsps.add( rsps.PATCH, api_url(f'/payments/{PAYMENT_DATA["uuid"]}/'), json={ **PAYMENT_DATA, 'status': 'failed', }, status=200, ) call_command('update_incomplete_payments', verbosity=0) self.assertEqual(rsps.calls[3].request.body.decode(), '{"status": "failed"}')
def test_prison_list(self): with responses.RequestsMock() as rsps, \ self.settings(CACHES={'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}}): mock_auth(rsps) rsps.add( rsps.GET, api_url('/prisons/'), json={ 'count': 2, 'results': [ { 'nomis_id': 'BBB', 'short_name': 'Prison 1', 'name': 'YOI Prison 1', }, { 'nomis_id': 'AAA', 'short_name': 'Prison 2', 'name': 'HMP Prison 2', }, ], }, ) response = self.client.get(reverse('help_area:prison_list')) self.assertIn('exclude_empty_prisons=True', rsps.calls[-1].request.url) self.assertContains(response, 'Prison 1') response = response.content.decode(response.charset) self.assertIn('Prison 2', response) self.assertLess(response.index('Prison 1'), response.index('Prison 2'))
def _test_update_incomplete_payments_doesnt_update_before_capture(self, settlement_summary): with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/'), json={ 'count': 1, 'results': [PAYMENT_DATA], }, status=200, ) rsps.add( rsps.GET, govuk_url(f'/payments/{PAYMENT_DATA["processor_id"]}/'), json={ 'reference': PAYMENT_DATA['uuid'], 'state': {'status': 'success'}, 'settlement_summary': settlement_summary, 'email': '*****@*****.**', }, status=200, ) call_command('update_incomplete_payments', verbosity=0) self.assertEqual(len(mail.outbox), 0)
def setUp(self): # NOTE: The structure of the API response is the same (e.g. with headers/results) but # the headers and obviously the data itself are not. # This is a simpler response to test conversion to CSV, caching, filtering etc... self.headers = { 'week_commencing': 'Week commencing', 'credits_total': 'Transactions – total', } api_response = { 'headers': self.headers, 'results': [ { 'week_commencing': '2021-06-07', 'credits_total': 100 }, { 'week_commencing': '2021-06-14' }, # Missing value (although it shouldn't happen) { 'credits_total': 200, 'week_commencing': '2021-06-21' }, # Different order ] } with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add(rsps.GET, f'{settings.API_URL}/performance/data/', json=api_response) self.response = self.client.get( reverse_lazy('performance_data_csv'))
def _test_captured_payment_doesnt_get_updated_before_capture( self, settlement_summary, mock_send_email): govuk_payment_data = { 'payment_id': PAYMENT_DATA['processor_id'], 'reference': PAYMENT_DATA['uuid'], 'state': { 'status': 'capturable' }, 'email': '*****@*****.**', } with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/'), json={ 'count': 1, 'results': [ { **PAYMENT_DATA, 'security_check': { 'status': 'accepted', 'user_actioned': True, }, }, ], }, status=200, ) rsps.add( rsps.GET, govuk_url(f'/payments/{PAYMENT_DATA["processor_id"]}/'), json=govuk_payment_data, status=200, ) rsps.add( rsps.POST, govuk_url( f'/payments/{PAYMENT_DATA["processor_id"]}/capture/'), status=204, ) rsps.add( rsps.GET, govuk_url(f'/payments/{PAYMENT_DATA["processor_id"]}/'), json={ **govuk_payment_data, 'state': { 'status': 'success' }, 'settlement_summary': settlement_summary, }, status=200, ) call_command('update_incomplete_payments', verbosity=0) mock_send_email.assert_not_called()
def assertFormValid(self, form): # noqa: N802 with mock.patch.object(self.form_class, 'get_api_session') as mock_session, responses.RequestsMock() as rsps: mock_session.side_effect = lambda reconnect: get_api_session() mock_auth(rsps) rsps.add( rsps.GET, api_url(f'/prisoner_account_balances/{form.prisoner_number}'), json={ 'combined_account_balance': 3000 }, status=200, ) self.assertTrue(form.is_valid(), msg='\n\n%s' % form.errors.as_text())
def _test_received_at_date_matches_captured_date( self, capture_submit_time, captured_date, received_at, mock_send_email, ): with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/'), json={ 'count': 1, 'results': [PAYMENT_DATA], }, status=200, ) rsps.add(rsps.GET, govuk_url(f'/payments/{PAYMENT_DATA["processor_id"]}/'), json={ 'reference': PAYMENT_DATA['uuid'], 'state': { 'status': 'success' }, 'settlement_summary': { 'capture_submit_time': capture_submit_time, 'captured_date': captured_date }, 'email': '*****@*****.**', }, status=200) rsps.add( rsps.PATCH, api_url(f'/payments/{PAYMENT_DATA["uuid"]}/'), json={ **PAYMENT_DATA, 'email': '*****@*****.**', }, status=200, ) call_command('update_incomplete_payments', verbosity=0) self.assertEqual( json.loads( rsps.calls[-1].request.body.decode())['received_at'], received_at) self.assertEqual(len(mock_send_email.call_args_list), 1)
def test_debit_card_payment_handles_api_errors(self): self.choose_debit_card_payment_method() self.fill_in_prisoner_details() self.fill_in_amount() with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.POST, api_url('/payments/'), status=500, ) with self.patch_prisoner_details_check(), self.silence_logger(): response = self.client.get(self.url, follow=False) self.assertContains(response, 'We’re sorry, your payment could not be processed on this occasion')
def test_debit_card_payment(self): self.choose_debit_card_payment_method() self.fill_in_prisoner_details() self.fill_in_amount() with responses.RequestsMock() as rsps: ref = 'wargle-blargle' processor_id = '3' mock_auth(rsps) rsps.add( rsps.POST, api_url('/payments/'), json={'uuid': ref}, status=201, ) rsps.add( rsps.POST, govuk_url('/payments/'), json={ 'payment_id': processor_id, '_links': { 'next_url': { 'method': 'GET', 'href': govuk_url(self.payment_process_path), } } }, status=201 ) rsps.add( rsps.PATCH, api_url('/payments/%s/' % ref), status=200, ) with self.patch_prisoner_details_check(): response = self.client.get(self.url, follow=False) # check amount and service charge submitted to api self.assertEqual(json.loads(rsps.calls[1].request.body)['amount'], 1700) self.assertEqual(json.loads(rsps.calls[1].request.body)['service_charge'], 61) # check total charge submitted to govuk self.assertEqual(json.loads(rsps.calls[2].request.body)['amount'], 1761) self.assertRedirects( response, govuk_url(self.payment_process_path), fetch_redirect_response=False )
def test_dont_send_email(self, mock_send_email): """ Test that the method only sends any email if the govuk payment status is 'capturable' and the MTP payment didn't have the email field set """ client = PaymentClient() payment = { 'uuid': 'some-id', } statuses = [ status for status in GovUkPaymentStatus if status != GovUkPaymentStatus.capturable ] with responses.RequestsMock() as rsps, silence_logger(): mock_auth(rsps) # API call related to updating the email address on the payment record rsps.add( rsps.PATCH, api_url(f'/payments/{payment["uuid"]}/'), json={ 'email': '*****@*****.**', }, status=200, ) for status in statuses: govuk_payment = { 'payment_id': 'payment-id', 'state': { 'status': status.name, # for status == 'errors' 'code': 'code', 'message': 'message', }, 'email': '*****@*****.**', } actual_status = client.complete_payment_if_necessary( payment, govuk_payment) self.assertEqual(actual_status, status) mock_send_email.assert_not_called()
def assertFormValid(self, form): # noqa: N802 with responses.RequestsMock() as rsps, \ mock.patch('send_money.forms.PrisonerDetailsForm.get_api_session') as mocked_api_session: mocked_api_session.side_effect = get_api_session mock_auth(rsps) rsps.add( rsps.GET, api_url('/prisoner_validity/'), json={ 'count': 1, 'results': [{ 'prisoner_number': 'A1234AB', 'prisoner_dob': '1980-10-05', }], }, status=200, ) self.assertTrue(form.is_valid(), msg='\n\n%s' % form.errors.as_text())
def test_confirmation_handles_rejected_card(self): self.choose_debit_card_payment_method() self.fill_in_prisoner_details() self.fill_in_amount() with responses.RequestsMock() as rsps: ref = 'wargle-blargle' processor_id = '3' mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/%s/' % ref), json={ 'processor_id': processor_id, 'recipient_name': 'John', 'amount': 1700, 'status': 'pending', 'created': datetime.datetime.now().isoformat() + 'Z', }, status=200, ) rsps.add( rsps.GET, govuk_url('/payments/%s/' % processor_id), json={ 'state': {'status': 'failed'}, 'email': '*****@*****.**', }, status=200 ) rsps.add( rsps.PATCH, api_url('/payments/%s/' % ref), status=200, ) with self.patch_prisoner_details_check(), self.silence_logger(): response = self.client.get( self.url, {'payment_ref': ref}, follow=True ) self.assertOnPage(response, 'check_details') # check session is kept for key in self.complete_session_keys: self.assertIn(key, self.client.session)
def test_update_incomplete_payments_doesnt_sent_email_if_no_captured_date( self, mock_send_email): """ Test that if the MTP payment is in 'pending' and the GOV.UK payment is in 'success' but no captured_date is found in the response, the MTP payment is not marked as successful yet and no email is sent. This is because the actual capture in Worldpay happens at a later time and can fail. The only way to find out if a payment really went through is by checking the captured date, when it becomes available. """ with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/'), json={ 'count': 1, 'results': [PAYMENT_DATA], }, status=200, ) rsps.add( rsps.GET, govuk_url(f'/payments/{PAYMENT_DATA["processor_id"]}/'), json={ 'reference': PAYMENT_DATA['uuid'], 'state': { 'status': 'success' }, 'email': '*****@*****.**', }, status=200, ) # no call to mark the payment as successful call_command('update_incomplete_payments', verbosity=0) mock_send_email.assert_not_called()
def test_confirmation_handles_govuk_errors(self): self.choose_debit_card_payment_method() self.fill_in_prisoner_details() self.fill_in_amount() with responses.RequestsMock() as rsps: ref = 'wargle-blargle' processor_id = '3' mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/%s/' % ref), json={ 'processor_id': processor_id, 'recipient_name': 'John', 'amount': 1700, 'status': 'pending', 'created': datetime.datetime.now().isoformat() + 'Z', }, status=200, ) rsps.add( rsps.GET, govuk_url('/payments/%s/' % processor_id), status=500 ) with self.patch_prisoner_details_check(), self.silence_logger(): response = self.client.get( self.url, {'payment_ref': ref}, follow=False ) self.assertContains(response, 'your payment could not be processed') self.assertContains(response, ref[:8].upper()) # check session is cleared for key in self.complete_session_keys: self.assertNotIn(key, self.client.session)
def test_date_filtering(self): with self.subTest('only "from" query parameter passed'): from_param = '2021-06-10' api_response = { 'headers': self.headers, 'results': [ { 'week_commencing': '2021-06-28', 'credits_total': 100 }, { 'week_commencing': '2021-07-05', 'credits_total': 200 }, ] } with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add(rsps.GET, f'{settings.API_URL}/performance/data/', json=api_response) query_params = f'?from={from_param}' response = self.client.get( reverse_lazy('performance_data_csv') + query_params) self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(response['Content-Type'], 'text/csv; charset=UTF-8') csv = response.content.decode('utf8') self.assertEqual( csv, 'Week commencing,Transactions – total\r\n2021-06-28,100\r\n2021-07-05,200\r\n' ) api_request = rsps.calls[1].request self.assertDictEqual(api_request.params, {'week__gte': from_param}) with self.subTest('both "from" and "to" query parameters passed'): from_param = '2021-06-10' to_param = '2021-07-01' api_response = { 'headers': self.headers, 'results': [ { 'week_commencing': '2021-06-28', 'credits_total': 100 }, ] } with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add(rsps.GET, f'{settings.API_URL}/performance/data/', json=api_response) query_params = f'?from={from_param}&to={to_param}' response = self.client.get( reverse_lazy('performance_data_csv') + query_params) self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(response['Content-Type'], 'text/csv; charset=UTF-8') csv = response.content.decode('utf8') self.assertEqual( csv, 'Week commencing,Transactions – total\r\n2021-06-28,100\r\n' ) api_request = rsps.calls[1].request self.assertDictEqual(api_request.params, { 'week__gte': from_param, 'week__lt': to_param })
def test_captured_payment_with_captured_date_gets_updated(self): """ Test that when a MTP pending payment is captured, if the captured date is immediately available, the payment is marked as 'taken' and a confirmation email is sent. """ govuk_payment_data = { 'payment_id': PAYMENT_DATA['processor_id'], 'reference': PAYMENT_DATA['uuid'], 'state': {'status': 'capturable'}, 'email': '*****@*****.**', } with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/'), json={ 'count': 1, 'results': [ { **PAYMENT_DATA, 'security_check': { 'status': 'accepted', 'user_actioned': True, }, }, ], }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url(f'/payments/{PAYMENT_DATA["processor_id"]}/'), json=govuk_payment_data, status=200, ) # capture payment rsps.add( rsps.POST, govuk_url(f'/payments/{PAYMENT_DATA["processor_id"]}/capture/'), status=204, ) # get govuk payment to see if we have the captured date rsps.add( rsps.GET, govuk_url(f'/payments/{PAYMENT_DATA["processor_id"]}/'), json={ **govuk_payment_data, 'state': {'status': 'success'}, 'settlement_summary': { 'capture_submit_time': '2016-10-27T15:11:05Z', 'captured_date': '2016-10-27', }, }, status=200, ) # update status rsps.add( rsps.PATCH, api_url(f'/payments/{PAYMENT_DATA["uuid"]}/'), json={ **PAYMENT_DATA, 'status': 'taken', 'email': '*****@*****.**', }, status=200, ) call_command('update_incomplete_payments', verbosity=0) self.assertEqual(len(mail.outbox), 1) self.assertEqual( json.loads(rsps.calls[-1].request.body.decode()), { 'status': 'taken', 'received_at': '2016-10-27T15:11:05+00:00', }, )
def test_update_incomplete_payments_extracts_card_details(self): """ Test that card details are extracted from the GOV.UK payment and saved on the MTP payment. """ payment_extra_details = { 'cardholder_name': 'Jack Halls', 'card_brand': 'Visa', 'worldpay_id': '11112222-1111-2222-3333-111122223333', 'card_number_first_digits': '100002', 'card_number_last_digits': '1111', 'card_expiry_date': '11/18', 'billing_address': { 'line1': '102 Petty France', 'line2': '', 'city': 'London', 'postcode': 'SW1H9AJ', 'country': 'GB', }, } with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/'), json={ 'count': 1, 'results': [PAYMENT_DATA], }, status=200, ) rsps.add( rsps.GET, govuk_url(f'/payments/{PAYMENT_DATA["processor_id"]}/'), json={ 'reference': PAYMENT_DATA['uuid'], 'state': {'status': 'success'}, 'settlement_summary': { 'capture_submit_time': '2016-10-27T15:11:05Z', 'captured_date': '2016-10-27', }, 'card_details': { 'card_brand': 'Visa', 'last_digits_card_number': '1111', 'first_digits_card_number': '100002', 'cardholder_name': 'Jack Halls', 'expiry_date': '11/18', 'billing_address': { 'line1': '102 Petty France', 'line2': '', 'postcode': 'SW1H9AJ', 'city': 'London', 'country': 'GB', }, }, 'provider_id': '11112222-1111-2222-3333-111122223333', 'email': '*****@*****.**', }, status=200, ) rsps.add( rsps.PATCH, api_url(f'/payments/{PAYMENT_DATA["uuid"]}/'), json={ **PAYMENT_DATA, **payment_extra_details, }, status=200, ) rsps.add( rsps.PATCH, api_url(f'/payments/{PAYMENT_DATA["uuid"]}/'), json={ **PAYMENT_DATA, **payment_extra_details, }, status=200, ) call_command('update_incomplete_payments', verbosity=0) self.assertDictEqual( json.loads(rsps.calls[-2].request.body.decode()), payment_extra_details, ) self.assertDictEqual( json.loads(rsps.calls[-1].request.body.decode()), { 'received_at': '2016-10-27T15:11:05+00:00', 'status': 'taken', }, )
def test_confirmation(self): self.choose_debit_card_payment_method() self.fill_in_prisoner_details() self.fill_in_amount() with responses.RequestsMock() as rsps: ref = 'wargle-blargle' processor_id = '3' mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/%s/' % ref), json={ 'processor_id': processor_id, 'recipient_name': 'John', 'amount': 1700, 'status': 'pending', 'created': datetime.datetime.now().isoformat() + 'Z', }, status=200, ) rsps.add( rsps.GET, govuk_url('/payments/%s/' % processor_id), json={ 'state': {'status': 'success'}, 'email': '*****@*****.**', '_links': { 'events': { 'method': 'GET', 'href': govuk_url('/payments/%s/events' % 1), } } }, status=200 ) rsps.add( rsps.GET, govuk_url('/payments/%s/events' % 1), json={ 'events': [ { 'state': {'status': 'success'}, 'updated': '2016-10-27T15:11:05.768Z' } ] }, status=200 ) rsps.add( rsps.PATCH, api_url('/payments/%s/' % ref), status=200, ) with self.patch_prisoner_details_check(): response = self.client.get( self.url, {'payment_ref': ref}, follow=False ) self.assertContains(response, 'success') # check session is cleared for key in self.complete_session_keys: self.assertNotIn(key, self.client.session) self.assertEqual('Send money to a prisoner: your payment was successful', mail.outbox[0].subject) self.assertTrue('WARGLE-B' in mail.outbox[0].body) self.assertTrue('£17' in mail.outbox[0].body) with self.patch_prisoner_details_check(): response = self.client.get( self.url, {'payment_ref': ref}, follow=True ) self.assertOnPage(response, 'choose_method')
def test_update_incomplete_payments(self): """ Test that incomplete payments get updated appropriately. - wargle-1111 relates to a GOV.UK payment in 'success' status so * should become 'taken' * a confirmation email should be sent - wargle-2222 relates to a GOV.UK payment in 'submitted' status so should be ignored - wargle-3333 relates to a GOV.UK payment in 'failed' status without being in capturable status in the past so should become 'failed' - wargle-4444 relates to a GOV.UK payment in 'cancelled' status so: * should become 'rejected' * a notification email should be sent to the sender - wargle-5555 relates to a GOV.UK payment in 'failed' status which was in a capturable status in the past so: * should become 'expired' * a notification email should be sent to the sender - wargle-6666 relates to a GOV.UK payment in 'success' status after its credit was accepted by FIU so: * should become 'taken' * a different confirmation email should be sent - wargle-7777 relates to a GOV.UK payment in 'failed' status which was in a capturable status in the past and was rejected by FIU so: * should become 'expired' * a rejection email (not a timeout email) should be sent to the sender """ payments = [ { 'uuid': 'wargle-1111', 'processor_id': 1, 'recipient_name': 'John', 'amount': 1700, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A1409AE', 'prisoner_dob': '1989-01-21', 'security_check': { 'status': 'accepted', 'user_actioned': False, }, }, { 'uuid': 'wargle-2222', 'processor_id': 2, 'recipient_name': 'Tom', 'amount': 2000, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A1234GJ', 'prisoner_dob': '1954-04-17', 'security_check': { 'status': 'accepted', 'user_actioned': False, }, }, { 'uuid': 'wargle-3333', 'processor_id': 3, 'recipient_name': 'Harry', 'amount': 500, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A5544CD', 'prisoner_dob': '1992-12-05', 'security_check': { 'status': 'accepted', 'user_actioned': False, }, }, { 'uuid': 'wargle-4444', 'processor_id': 4, 'recipient_name': 'Lisa', 'amount': 600, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A4444DB', 'prisoner_dob': '1992-12-05', 'security_check': { 'status': 'accepted', 'user_actioned': False, }, }, { 'uuid': 'wargle-5555', 'processor_id': 5, 'recipient_name': 'Tom', 'amount': 700, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A4444DE', 'prisoner_dob': '1992-12-05', 'security_check': { 'status': 'accepted', 'user_actioned': False, }, }, { 'uuid': 'wargle-6666', 'processor_id': 6, 'recipient_name': 'Tim', 'amount': 800, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A1409AW', 'prisoner_dob': '1989-01-21', 'security_check': { 'status': 'accepted', 'user_actioned': True, }, }, { 'uuid': 'wargle-7777', 'processor_id': 7, 'recipient_name': 'Jim', 'amount': 900, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A4444DQ', 'prisoner_dob': '1992-12-05', 'security_check': { 'status': 'rejected', 'user_actioned': True, }, }, ] with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/'), json={ 'count': len(payments), 'results': payments, }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 1), json={ 'reference': 'wargle-1111', 'state': {'status': 'success'}, 'settlement_summary': { 'capture_submit_time': '2016-10-27T15:11:05Z', 'captured_date': '2016-10-27' }, 'email': '*****@*****.**', }, status=200, ) # save email rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-1111'), json={ **payments[0], 'email': '*****@*****.**', }, status=200, ) # update status rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-1111'), json={ **payments[0], 'email': '*****@*****.**', 'status': 'taken', }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 2), json={ 'reference': 'wargle-2222', 'state': {'status': 'submitted'}, 'settlement_summary': { 'capture_submit_time': '2016-10-27T15:11:05Z', 'captured_date': None }, 'email': '*****@*****.**', }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 3), json={ 'reference': 'wargle-3333', 'state': {'status': 'failed'}, 'settlement_summary': { 'capture_submit_time': None, 'captured_date': None }, 'email': '*****@*****.**', }, status=200, ) # update status rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-3333'), json={ **payments[2], 'status': 'failed', }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 4), json={ 'reference': 'wargle-4444', 'state': {'status': 'cancelled'}, 'email': '*****@*****.**', }, status=200, ) # update status rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-4444'), json={ **payments[3], 'status': 'rejected', }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 5), json={ 'payment_id': 'wargle-5555', 'reference': 'wargle-5555', 'state': { 'status': 'failed', 'code': 'P0020', }, 'email': '*****@*****.**', }, status=200, ) # get govuk payment events rsps.add( rsps.GET, govuk_url('/payments/%s/events/' % 'wargle-5555'), status=200, json={ 'events': [ { 'payment_id': 'wargle-5555', 'state': { 'status': 'capturable', 'finished': False, }, }, ], 'payment_id': 'wargle-5555', }, ) # update status rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-5555'), json={ **payments[4], 'status': 'expired', }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 6), json={ 'reference': 'wargle-6666', 'state': {'status': 'success'}, 'settlement_summary': { 'capture_submit_time': '2016-10-27T15:11:05Z', 'captured_date': '2016-10-27' }, 'email': '*****@*****.**', }, status=200, ) # save email rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-6666'), json={ **payments[5], 'email': '*****@*****.**', }, status=200, ) # update status rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-6666'), json={ **payments[5], 'status': 'taken', }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 7), json={ 'payment_id': 'wargle-7777', 'reference': 'wargle-7777', 'state': { 'status': 'failed', 'code': 'P0020', }, 'email': '*****@*****.**', }, status=200, ) # get govuk payment events rsps.add( rsps.GET, govuk_url('/payments/%s/events/' % 'wargle-7777'), status=200, json={ 'events': [ { 'payment_id': 'wargle-7777', 'state': { 'status': 'capturable', 'finished': False, }, }, ], 'payment_id': 'wargle-7777', }, ) # update status rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-7777'), json={ **payments[0], 'status': 'expired', }, status=200, ) call_command('update_incomplete_payments', verbosity=0) # check wargle-1111 self.assertEqual( json.loads(rsps.calls[3].request.body.decode()), {'email': '*****@*****.**'}, ) self.assertDictEqual( json.loads(rsps.calls[4].request.body.decode()), { 'status': 'taken', 'received_at': '2016-10-27T15:11:05+00:00', }, ) self.assertEqual( mail.outbox[0].subject, 'Send money to someone in prison: your payment was successful', ) self.assertTrue('John' in mail.outbox[0].body) self.assertTrue('£17' in mail.outbox[0].body) # check wargle-3333 self.assertEqual( json.loads(rsps.calls[7].request.body.decode()), { 'email': '*****@*****.**', 'status': 'failed', }, ) # check wargle-4444 self.assertEqual( json.loads(rsps.calls[9].request.body.decode()), { 'email': '*****@*****.**', 'status': 'rejected', }, ) self.assertEqual( mail.outbox[1].subject, 'Send money to someone in prison: your payment has NOT been sent to the prisoner', ) self.assertTrue('Lisa' in mail.outbox[1].body) self.assertTrue('£6' in mail.outbox[1].body) # check wargle-5555 self.assertEqual( json.loads(rsps.calls[12].request.body.decode()), { 'email': '*****@*****.**', 'status': 'expired', }, ) self.assertEqual( mail.outbox[2].subject, 'Send money to someone in prison: payment session expired', ) self.assertTrue('Tom' in mail.outbox[2].body) self.assertTrue('£7' in mail.outbox[2].body) # check wargle-6666 self.assertEqual( json.loads(rsps.calls[14].request.body.decode()), {'email': '*****@*****.**'}, ) self.assertDictEqual( json.loads(rsps.calls[15].request.body.decode()), { 'status': 'taken', 'received_at': '2016-10-27T15:11:05+00:00', }, ) self.assertEqual( mail.outbox[3].subject, 'Send money to someone in prison: your payment has now gone through', ) self.assertTrue('Tim' in mail.outbox[3].body) self.assertTrue('£8' in mail.outbox[3].body) # check wargle-7777 self.assertEqual( json.loads(rsps.calls[18].request.body.decode()), { 'email': '*****@*****.**', 'status': 'expired', }, ) self.assertEqual( mail.outbox[4].subject, 'Send money to someone in prison: your payment has NOT been sent to the prisoner', ) self.assertTrue('Jim' in mail.outbox[4].body) self.assertTrue('£9' in mail.outbox[4].body) # double-check that no more emails were sent self.assertEqual(len(mail.outbox), 5)
def test_skip_payments(self): """ Test that checks for some payments get skipped and some get included - wargle-aaaa is a recent payment and the check is in pending so should be skipped - wargle-bbbb is an old payment so should be checked even if the check is in pending as it could have been timed out - wargle-cccc is a recent payment and the payment object doesn't have check info so should be included just in case - wargle-dddd is a recent payment and the check is in accepted so should be included - wargle-eeee is a recent payment and the check is in rejected so should be included """ payments = [ { 'uuid': 'wargle-aaaa', 'processor_id': 1, 'recipient_name': 'John', 'amount': 1700, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A1409AE', 'prisoner_dob': '1989-01-21', 'security_check': { 'status': 'pending', 'user_actioned': False, }, }, { 'uuid': 'wargle-bbbb', 'processor_id': 2, 'recipient_name': 'Tom', 'amount': 2000, 'status': 'pending', 'created': ( datetime.now() - ALWAYS_CHECK_IF_OLDER_THAN - timedelta(hours=1) ).isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A1234GJ', 'prisoner_dob': '1954-04-17', 'security_check': { 'status': 'pending', 'user_actioned': False, }, }, { 'uuid': 'wargle-cccc', 'processor_id': 3, 'recipient_name': 'Harry', 'amount': 500, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A5544CD', 'prisoner_dob': '1992-12-05', 'security_check': None, }, { 'uuid': 'wargle-dddd', 'processor_id': 4, 'recipient_name': 'Lisa', 'amount': 600, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A4444DB', 'prisoner_dob': '1992-12-05', 'security_check': { 'status': 'accepted', 'user_actioned': False, }, }, { 'uuid': 'wargle-eeee', 'processor_id': 5, 'recipient_name': 'Tom', 'amount': 700, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z', 'modified': datetime.now().isoformat() + 'Z', 'prisoner_number': 'A4444DE', 'prisoner_dob': '1992-12-05', 'security_check': { 'status': 'rejected', 'user_actioned': True, }, }, ] with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/'), json={ 'count': len(payments), 'results': payments, }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 2), json={ 'reference': 'wargle-bbbb', 'state': {'status': 'capturable'}, }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 3), json={ 'reference': 'wargle-cccc', 'state': {'status': 'submitted'}, 'settlement_summary': { 'capture_submit_time': '2016-10-27T15:11:05Z', 'captured_date': None }, }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 4), json={ 'reference': 'wargle-dddd', 'state': {'status': 'success'}, 'settlement_summary': { 'capture_submit_time': '2016-10-27T15:11:05Z', 'captured_date': '2016-10-27' }, 'email': '*****@*****.**', }, status=200, ) # save email rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-dddd'), json={ **payments[3], 'email': '*****@*****.**', }, status=200, ) # update status rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-dddd'), json={ **payments[3], 'email': '*****@*****.**', 'status': 'taken', }, status=200, ) # get govuk payment rsps.add( rsps.GET, govuk_url('/payments/%s/' % 5), json={ 'reference': 'wargle-eeee', 'state': {'status': 'cancelled'}, 'email': '*****@*****.**', }, status=200, ) # update status rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-eeee'), json={ **payments[4], 'status': 'rejected', }, status=200, ) call_command('update_incomplete_payments', verbosity=0) # wargle-aaaa, wargle-bbbb and wargle-cccc already checked implicitly by RequestsMock # check wargle-dddd self.assertEqual( json.loads(rsps.calls[5].request.body.decode()), {'email': '*****@*****.**'}, ) self.assertDictEqual( json.loads(rsps.calls[6].request.body.decode()), { 'status': 'taken', 'received_at': '2016-10-27T15:11:05+00:00', }, ) self.assertEqual( mail.outbox[0].subject, 'Send money to someone in prison: your payment was successful', ) self.assertTrue('Lisa' in mail.outbox[0].body) self.assertTrue('£6' in mail.outbox[0].body) # check wargle-eeee self.assertEqual( json.loads(rsps.calls[8].request.body.decode()), { 'email': '*****@*****.**', 'status': 'rejected', }, ) self.assertEqual( mail.outbox[1].subject, 'Send money to someone in prison: your payment has NOT been sent to the prisoner', ) self.assertTrue('Tom' in mail.outbox[1].body) self.assertTrue('£7' in mail.outbox[1].body) # double-check that no more emails were sent self.assertEqual(len(mail.outbox), 2)
def test_update_incomplete_payments(self): with responses.RequestsMock() as rsps: mock_auth(rsps) rsps.add( rsps.GET, api_url('/payments/'), json={ 'count': 3, 'results': [ { 'uuid': 'wargle-1111', 'processor_id': 1, 'recipient_name': 'John', 'amount': 1700, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z' }, { 'uuid': 'wargle-2222', 'processor_id': 2, 'recipient_name': 'Tom', 'amount': 2000, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z' }, { 'uuid': 'wargle-3333', 'processor_id': 3, 'recipient_name': 'Harry', 'amount': 500, 'status': 'pending', 'created': datetime.now().isoformat() + 'Z' }, ] }, status=200, ) rsps.add( rsps.GET, govuk_url('/payments/%s/' % 1), json={ 'state': {'status': 'success'}, 'email': '*****@*****.**', '_links': { 'events': { 'method': 'GET', 'href': govuk_url('/payments/%s/events' % 1), } } }, status=200 ) rsps.add( rsps.GET, govuk_url('/payments/%s/events' % 1), json={ 'events': [ { 'state': {'status': 'success'}, 'updated': '2016-10-27T15:11:05.768Z' } ] }, status=200 ) rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-1111'), status=200, ) rsps.add( rsps.GET, govuk_url('/payments/%s/' % 2), json={ 'state': {'status': 'submitted'}, 'email': '*****@*****.**', }, status=200 ) rsps.add( rsps.GET, govuk_url('/payments/%s/' % 3), json={ 'state': {'status': 'failed'}, 'email': '*****@*****.**', }, status=200 ) rsps.add( rsps.PATCH, api_url('/payments/%s/' % 'wargle-3333'), status=200, ) call_command('update_incomplete_payments')
def test_success_page(self): ref = 'f469ec29-fb86-40db-a9e0-6faa409533be' processor_id = '3' with responses.RequestsMock( ) as rsps, self.patch_view_chain_form_checking(): mock_auth(rsps) rsps.add(rsps.GET, api_url('/payments/%s/' % ref), json={ 'uuid': ref, 'processor_id': processor_id, 'recipient_name': 'James Bond', 'amount': 2000, 'status': 'pending', 'created': datetime.datetime.now().isoformat() + 'Z', 'prisoner_number': 'A5544CD', 'prisoner_dob': '1992-12-05', }) rsps.add(rsps.GET, govuk_url('/payments/%s' % processor_id), json={ 'reference': ref, 'state': { 'status': 'success', 'finished': True }, 'amount': 2000, 'payment_id': processor_id, 'email': '*****@*****.**', '_links': { 'events': { 'href': govuk_url('/payments/%s/events' % processor_id), 'method': 'GET' } } }) rsps.add(rsps.PATCH, api_url('/payments/%s/' % ref), json={ 'uuid': ref, 'processor_id': processor_id, 'recipient_name': 'James Bond', 'amount': 2000, 'status': 'pending', 'created': datetime.datetime.now().isoformat() + 'Z', 'prisoner_number': 'A5544CD', 'prisoner_dob': '1992-12-05', 'email': '*****@*****.**', }) self.driver.get(self.live_server_url + '/en-gb/debit-card/confirmation/?payment_ref=' + ref) self.assertInSource('Payment successful') self.assertInSource('<strong>F469EC29</strong>') self.assertInSource('James Bond') self.assertInSource('£20') self.assertInSource('Print this page')
def test_capturable_payment_that_should_be_cancelled( self, mock_send_email): """ Test that if the govuk payment is in 'capturable' state and the payment should be cancelled: - the MTP payment record is patched with the card details attributes if necessary - the method cancels the payment - no email is sent - the method returns GovUkPaymentStatus.cancelled """ client = PaymentClient() payment = { 'uuid': 'some-id', 'recipient_name': 'Alice Re', 'prisoner_number': 'AAB0A00', 'prisoner_name': 'John Doe', 'amount': 1700, 'security_check': { 'status': 'rejected', 'user_actioned': True, }, } payment_extra_details = { 'email': '*****@*****.**', 'worldpay_id': '123456789', 'cardholder_name': 'John Doe', 'card_number_first_digits': '1234', 'card_number_last_digits': '987', 'card_expiry_date': '01/20', 'card_brand': 'visa', 'billing_address': { 'line1': '102 Petty France', 'line2': '', 'postcode': 'SW1H9AJ', 'city': 'London', 'country': 'GB', }, } govuk_payment = { 'payment_id': 'payment-id', 'state': { 'status': GovUkPaymentStatus.capturable.name, }, 'email': '*****@*****.**', 'provider_id': '123456789', 'card_details': { 'cardholder_name': 'John Doe', 'first_digits_card_number': '1234', 'last_digits_card_number': '987', 'expiry_date': '01/20', 'card_brand': 'visa', 'billing_address': { 'line1': '102 Petty France', 'line2': '', 'postcode': 'SW1H9AJ', 'city': 'London', 'country': 'GB', }, }, } with responses.RequestsMock() as rsps: mock_auth(rsps) # API call related to updating the email address and card details rsps.add( rsps.PATCH, api_url(f'/payments/{payment["uuid"]}/'), json={ **payment, **payment_extra_details, }, status=200, ) rsps.add( rsps.POST, govuk_url(f'/payments/{govuk_payment["payment_id"]}/cancel/'), status=204, ) status = client.complete_payment_if_necessary( payment, govuk_payment) payment_patch_body = json.loads( rsps.calls[-2].request.body.decode()) self.assertDictEqual( payment_patch_body, payment_extra_details, ) self.assertEqual(status, GovUkPaymentStatus.cancelled) mock_send_email.assert_not_called()
def test_capturable_payment_that_shouldnt_be_captured_yet( self, mock_send_email): """ Test that if the govuk payment is in 'capturable' state, the MTP payment record doesn't have the email field filled in and the payment should not be captured yet: - the MTP payment record is patched with the card details attributes - the method returns GovUkPaymentStatus.capturable - an email is sent to the sender """ client = PaymentClient() payment = { 'uuid': 'b74a0eb6-0437-4b22-bce8-e6f11bd43802', 'recipient_name': 'Alice Re', 'prisoner_name': 'John Doe', 'prisoner_number': 'AAB0A00', 'amount': 1700, 'security_check': { 'status': 'pending', 'user_actioned': False, }, } payment_extra_details = { 'email': '*****@*****.**', 'worldpay_id': '123456789', 'cardholder_name': 'John Doe', 'card_number_first_digits': '1234', 'card_number_last_digits': '987', 'card_expiry_date': '01/20', 'card_brand': 'visa', 'billing_address': { 'line1': '102 Petty France', 'line2': '', 'postcode': 'SW1H9AJ', 'city': 'London', 'country': 'GB', }, } govuk_payment = { 'payment_id': 'payment-id', 'state': { 'status': GovUkPaymentStatus.capturable.name, }, 'email': '*****@*****.**', 'provider_id': '123456789', 'card_details': { 'cardholder_name': 'John Doe', 'first_digits_card_number': '1234', 'last_digits_card_number': '987', 'expiry_date': '01/20', 'card_brand': 'visa', 'billing_address': { 'line1': '102 Petty France', 'line2': '', 'postcode': 'SW1H9AJ', 'city': 'London', 'country': 'GB', }, }, } with responses.RequestsMock() as rsps: mock_auth(rsps) # API call related to updating the email address and card details rsps.add( rsps.PATCH, api_url(f'/payments/{payment["uuid"]}/'), json={ **payment, **payment_extra_details, }, status=200, ) status = client.complete_payment_if_necessary( payment, govuk_payment) payment_patch_body = json.loads( rsps.calls[-1].request.body.decode()) self.assertDictEqual( payment_patch_body, payment_extra_details, ) self.assertEqual(status, GovUkPaymentStatus.capturable) self.assertEqual(len(mock_send_email.call_args_list), 1) send_email_kwargs = mock_send_email.call_args_list[0].kwargs self.assertEqual(send_email_kwargs['template_name'], 'send-money-debit-card-payment-on-hold') self.assertEqual(send_email_kwargs['to'], '*****@*****.**')
def test_success_status(self, mock_send_email): """ Test that if the govuk payment is in 'success' state and the MTP payment record doesn't have all the card details and email field filled in: - the MTP payment record is patched with the extra payment details - the method returns GovUkPaymentStatus.success - no email is sent """ client = PaymentClient() payment = { 'uuid': 'some-id', } payment_extra_details = { 'email': '*****@*****.**', 'cardholder_name': 'John Doe', 'card_number_first_digits': '1234', 'card_number_last_digits': '987', 'card_expiry_date': '01/20', 'card_brand': 'visa', 'billing_address': { 'line1': '102 Petty France', 'line2': '', 'postcode': 'SW1H9AJ', 'city': 'London', 'country': 'GB', }, } govuk_payment = { 'payment_id': 'payment-id', 'state': { 'status': GovUkPaymentStatus.success.name, }, 'email': '*****@*****.**', 'card_details': { 'cardholder_name': 'John Doe', 'first_digits_card_number': '1234', 'last_digits_card_number': '987', 'expiry_date': '01/20', 'card_brand': 'visa', 'billing_address': { 'line1': '102 Petty France', 'line2': '', 'postcode': 'SW1H9AJ', 'city': 'London', 'country': 'GB', }, }, } with responses.RequestsMock() as rsps: mock_auth(rsps) # API call related to updating the email address and other details on the payment record rsps.add( rsps.PATCH, api_url(f'/payments/{payment["uuid"]}/'), json={ **payment, **payment_extra_details, }, status=200, ) status = client.complete_payment_if_necessary( payment, govuk_payment) self.assertDictEqual( json.loads(rsps.calls[-1].request.body.decode()), payment_extra_details, ) self.assertEqual(status, GovUkPaymentStatus.success) mock_send_email.assert_not_called()