def test_with_none_payment(self):
        """
        Test that it returns the non-falsy attrs in govuk_payment if the passed-in payment is falsy.
        """
        client = PaymentClient()

        payment = None
        govuk_payment = {
            'email': '*****@*****.**',
            'provider_id': '',
            'card_details': {
                'cardholder_name': None,
                'card_brand': 'visa',
            },
            'extra_attribute': 'some-value',
        }
        attr_updates = client.get_completion_payment_attr_updates(payment, govuk_payment)

        self.assertEqual(
            attr_updates,
            {
                'email': '*****@*****.**',
                'card_brand': 'visa',
            }
        )
Example #2
0
    def test_conflict(self, mock_send_email):
        """
        Test that if GOV.UK Pay returns 409 when cancelling a payment, the method raises an HTTPError.
        """
        client = PaymentClient()

        payment_id = 'invalid'
        govuk_payment = {
            'payment_id': payment_id,
            'state': {
                'status': GovUkPaymentStatus.capturable.name,
            },
        }
        with responses.RequestsMock() as rsps:
            rsps.add(
                rsps.POST,
                govuk_url(f'/payments/{payment_id}/cancel/'),
                status=409,
            )

            with self.assertRaises(HTTPError) as e:
                client.cancel_govuk_payment(govuk_payment)

            self.assertEqual(
                e.exception.response.status_code,
                409,
            )

        mock_send_email.assert_not_called()
Example #3
0
    def test_capturable_payment_that_shouldnt_be_captured_yet_with_email_already_set(
            self, mock_send_email):
        """
        Test that if the govuk payment is in 'capturable' state, the MTP payment record
        has already the email field filled in and the payment should not be captured yet:

        - the method returns GovUkPaymentStatus.capturable
        - no email is sent as it
        """
        client = PaymentClient()

        payment = {
            'uuid': 'some-id',
            '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',
            },
            'security_check': {
                'status': 'pending',
                'user_actioned': False,
            },
        }
        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',
                },
            },
        }

        status = client.complete_payment_if_necessary(payment, govuk_payment)

        self.assertEqual(status, GovUkPaymentStatus.capturable)
        mock_send_email.assert_not_called()
    def get(self, request):
        prisoner_details = self.valid_form_data[
            DebitCardPrisonerDetailsView.url_name]
        amount_details = self.valid_form_data[DebitCardAmountView.url_name]

        amount_pence = int(amount_details['amount'] * 100)
        service_charge_pence = int(
            get_service_charge(amount_details['amount']) * 100)
        user_ip = request.META.get('HTTP_X_FORWARDED_FOR', '')
        user_ip = user_ip.split(',')[0].strip() or None

        payment_ref = None
        failure_context = {'short_payment_ref': _('Not known')}
        try:
            payment_client = PaymentClient()
            new_payment = {
                'amount': amount_pence,
                'service_charge': service_charge_pence,
                'recipient_name': prisoner_details['prisoner_name'],
                'prisoner_number': prisoner_details['prisoner_number'],
                'prisoner_dob': prisoner_details['prisoner_dob'].isoformat(),
                'ip_address': user_ip,
            }
            payment_ref = payment_client.create_payment(new_payment)
            failure_context['short_payment_ref'] = payment_ref[:8]

            new_govuk_payment = {
                'delayed_capture':
                should_be_capture_delayed(),
                'amount':
                amount_pence + service_charge_pence,
                'reference':
                payment_ref,
                'description':
                gettext('To this prisoner: %(prisoner_number)s' %
                        prisoner_details),
                'return_url':
                site_url(
                    build_view_url(self.request,
                                   DebitCardConfirmationView.url_name)) +
                '?payment_ref=' + payment_ref,
            }
            if new_govuk_payment['delayed_capture']:
                logger.info('Starting delayed capture for %(payment_ref)s',
                            {'payment_ref': payment_ref})

            govuk_payment = payment_client.create_govuk_payment(
                payment_ref, new_govuk_payment)
            if govuk_payment:
                return redirect(get_link_by_rel(govuk_payment, 'next_url'))
        except OAuth2Error:
            logger.exception('Authentication error')
        except RequestException:
            logger.exception('Failed to create new payment (ref %s)',
                             payment_ref)

        return render(request, 'send_money/debit-card-error.html',
                      failure_context)
Example #5
0
    def test_do_nothing_if_govukpayment_is_falsy(self, mock_send_email):
        """
        Test that if the passed in govuk payment dict is falsy, the method doesn't do anything.
        """
        client = PaymentClient()

        govuk_payment = {}
        returned_status = client.cancel_govuk_payment(govuk_payment)
        self.assertEqual(returned_status, None)

        mock_send_email.assert_not_called()
    def test_do_nothing_if_govukpayment_is_falsy(self):
        """
        Test that if the passed in govuk payment dict is falsy, the method doesn't do anything.
        """
        client = PaymentClient()

        govuk_payment = {}
        returned_status = client.cancel_govuk_payment(govuk_payment)
        self.assertEqual(returned_status, None)

        self.assertEqual(len(mail.outbox), 0)
Example #7
0
    def test_do_nothing_if_govukpayment_is_falsy(self, mock_send_email):
        """
        Test that if the passed in govuk payment dict is falsy, the method returns None and
        doesn't send any email.
        """
        client = PaymentClient()

        payment = {}
        govuk_payment = {}
        status = client.complete_payment_if_necessary(payment, govuk_payment)

        self.assertEqual(status, None)
        mock_send_email.assert_not_called()
    def test_get(self):
        """
        Test that the completion values in govuk_payment that are not already set in payment
        are returned.
        """
        client = PaymentClient()

        payment = {
            'email': '*****@*****.**',  # shouldn't get overridden
            'worldpay_id': '',  # should get updated
            'cardholder_name': None,  # should get updated
            'card_brand': 'visa',  # hasn't changed so should be ignored
        }
        govuk_payment = {
            'email': '*****@*****.**',  # should be ignored
            'provider_id': '123456789',  # should be used
            'card_details': {
                'cardholder_name': 'John Doe',
                'first_digits_card_number': '1234',
                'last_digits_card_number': '987',
                'expiry_date': '01/20',
                'card_brand': 'visa',  # hasn't changed so should be ignored
                'billing_address': {
                    'line1': '102 Petty France',
                    'line2': '',
                    'postcode': 'SW1H9AJ',
                    'city': 'London',
                    'country': 'GB',
                },
            },
            'extra_attribute': 'some-value',
        }
        attr_updates = client.get_completion_payment_attr_updates(payment, govuk_payment)

        self.assertEqual(
            attr_updates,
            {
                'worldpay_id': '123456789',
                'cardholder_name': 'John Doe',
                'card_number_first_digits': '1234',
                'card_number_last_digits': '987',
                'card_expiry_date': '01/20',
                'billing_address': {
                    'line1': '102 Petty France',
                    'line2': '',
                    'postcode': 'SW1H9AJ',
                    'city': 'London',
                    'country': 'GB',
                },
            }
        )
    def test_with_none_govuk_payment(self):
        """
        Test that it returns {} if the passed in govuk payment is falsy.
        """
        client = PaymentClient()

        payment = {
            'worldpay_id': '123456789',
            'card_brand': 'visa',
        }
        govuk_payment = None
        attr_updates = client.get_completion_payment_attr_updates(payment, govuk_payment)

        self.assertEqual(attr_updates, {})
Example #10
0
    def test_500(self):
        """
        Test that if GOV.UK Pay returns 500, the method raises HTTPError.
        """
        payment_id = 'payment-id'

        client = PaymentClient()
        with responses.RequestsMock() as rsps:
            rsps.add(
                rsps.GET,
                govuk_url(f'/payments/{payment_id}/events/'),
                status=500,
            )

            with self.assertRaises(HTTPError):
                client.get_govuk_payment_events(payment_id)
Example #11
0
    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()
Example #12
0
    def test_invalid_response(self):
        """
        Test that if the GOV.UK Pay response doesn't have the expected structure, the method raises RequestException.
        """
        payment_id = 'payment-id'

        client = PaymentClient()
        with responses.RequestsMock() as rsps:
            rsps.add(rsps.GET,
                     govuk_url(f'/payments/{payment_id}/events/'),
                     status=200,
                     json={
                         'unexpected-key': 'unexpected-value',
                     })

            with self.assertRaises(RequestException):
                client.get_govuk_payment_events(payment_id)
Example #13
0
    def test_successful(self):
        """
        Test that the method returns events information about a certain govuk payment.
        """
        payment_id = 'payment-id'
        expected_events = [
            {
                'payment_id': payment_id,
                'state': {
                    'status': 'created',
                    'finished': True,
                    'message': 'User cancelled the payment',
                    'code': 'P010',
                },
                'updated': '2017-01-10T16:44:48.646Z',
                '_links': {
                    'payment_url': {
                        'href':
                        'https://an.example.link/from/payment/platform',
                        'method': 'GET',
                    },
                },
            },
        ]

        client = PaymentClient()
        with responses.RequestsMock() as rsps:
            rsps.add(
                rsps.GET,
                govuk_url(f'/payments/{payment_id}/events/'),
                status=200,
                json={
                    'events': expected_events,
                    'payment_id': payment_id,
                    '_links': {
                        'self': {
                            'hrefTrue':
                            'https://an.example.link/from/payment/platform',
                            'method': 'GET',
                        },
                    },
                })

            actual_events = client.get_govuk_payment_events(payment_id)

        self.assertListEqual(actual_events, expected_events)
    def get(self, request, *args, **kwargs):
        payment_ref = self.request.GET.get('payment_ref')
        try:
            # check payment status
            payment_client = PaymentClient()
            payment = payment_client.get_payment(payment_ref)
            if not payment or payment['status'] != 'pending':
                # bail out if accessed without specifying a payment in pending state
                return clear_session_view(request)

            kwargs.update({
                'short_payment_ref': payment_ref[:8].upper(),
                'prisoner_name': payment['recipient_name'],
                'amount': decimal.Decimal(payment['amount']) / 100,
                'email_sent': False,
            })

            # check gov.uk payment status
            govuk_id = payment['processor_id']
            self.success, kwargs = payment_client.check_govuk_payment_status(
                payment_ref, govuk_id, kwargs
            )
            if not self.success:
                return redirect(self.build_view_url(DebitCardCheckView.url_name))
        except OAuth2Error:
            logger.exception('Authentication error while processing %s' % payment_ref)
        except SlumberHttpBaseException as error:
            error_message = 'Error while processing %s' % payment_ref
            if hasattr(error, 'content'):
                error_message += '\nReceived: %s' % error.content
            logger.exception(error_message)
        except RequestsTimeout:
            logger.exception('GOV.UK Pay payment check timed out for %s' % payment_ref)
        except RequestException as error:
            error_message = 'GOV.UK Pay payment check failed for %s' % payment_ref
            if hasattr(error, 'response') and hasattr(error.response, 'content'):
                error_message += '\nReceived: %s' % error.response.content
            logger.exception(error_message)
        except GovUkPaymentStatusException:
            logger.exception('GOV.UK Pay payment status incomplete for %s' % payment_ref)

        response = super().get(request, *args, **kwargs)
        request.session.flush()
        return response
    def get(self, request):
        prisoner_details = self.valid_form_data[DebitCardPrisonerDetailsView.url_name]
        amount_details = self.valid_form_data[DebitCardAmountView.url_name]

        amount_pence = int(amount_details['amount'] * 100)
        service_charge_pence = int(get_service_charge(amount_details['amount']) * 100)
        payment_ref = None
        failure_context = {
            'short_payment_ref': _('Not known')
        }
        try:
            payment_client = PaymentClient()
            new_payment = {
                'amount': amount_pence,
                'service_charge': service_charge_pence,
                'recipient_name': prisoner_details['prisoner_name'],
                'prisoner_number': prisoner_details['prisoner_number'],
                'prisoner_dob': prisoner_details['prisoner_dob'].isoformat(),
            }
            payment_ref = payment_client.create_payment(new_payment)
            failure_context['short_payment_ref'] = payment_ref[:8]

            new_govuk_payment = {
                'amount': amount_pence + service_charge_pence,
                'reference': payment_ref,
                'description': gettext('To this prisoner: %(prisoner_number)s' % prisoner_details),
                'return_url': site_url(
                    self.build_view_url(DebitCardConfirmationView.url_name) + '?payment_ref=' + payment_ref
                ),
            }
            govuk_payment = payment_client.create_govuk_payment(payment_ref, new_govuk_payment)
            if govuk_payment:
                return redirect(get_link_by_rel(govuk_payment, 'next_url'))
        except OAuth2Error:
            logger.exception('Authentication error')
        except SlumberHttpBaseException:
            logger.exception('Failed to create new payment')
        except RequestsTimeout:
            logger.exception(
                'GOV.UK Pay payment initiation timed out for %s' % payment_ref
            )

        return render(request, 'send_money/debit-card-failure.html', failure_context)
Example #16
0
    def test_do_nothing_if_payment_in_finished_state(self, mock_send_email):
        """
        Test that if the govuk payment is already in a finished state, the method doesn't
        do anything.
        """
        finished_statuses = [
            status for status in GovUkPaymentStatus if status.finished()
        ]
        for status in finished_statuses:
            govuk_payment = {
                'payment_id': 'payment-id',
                'state': {
                    'status': status.name,
                },
            }

            client = PaymentClient()
            returned_status = client.cancel_govuk_payment(govuk_payment)
            self.assertEqual(returned_status, status)

            mock_send_email.assert_not_called()
    def test_capture(self):
        """
        Test that if the govuk payment is in 'capturable' state, the method captures the payment
        and no email is sent.

        If the method is called again, nothing happen so that to avoid side effects.
        """
        client = PaymentClient()

        payment_id = 'payment-id'
        govuk_payment = {
            'payment_id': payment_id,
            'state': {
                'status': GovUkPaymentStatus.capturable.name,
            },
            'email': '*****@*****.**',

        }
        with responses.RequestsMock() as rsps:
            rsps.add(
                rsps.POST,
                govuk_url(f'/payments/{payment_id}/capture/'),
                status=204,
            )

            returned_status = client.capture_govuk_payment(govuk_payment)

        self.assertEqual(returned_status, GovUkPaymentStatus.success)
        self.assertEqual(
            govuk_payment['state']['status'],
            GovUkPaymentStatus.success.name,
        )

        self.assertEqual(len(mail.outbox), 0)

        # try to capture the payment again, nothing should happen
        client.capture_govuk_payment(govuk_payment)
        self.assertEqual(len(mail.outbox), 0)
Example #18
0
    def perform_update(self):
        payment_client = PaymentClient()
        payments = payment_client.get_incomplete_payments()
        for payment in payments:
            if not self.should_be_checked(payment):
                continue

            payment_ref = payment['uuid']
            govuk_id = payment['processor_id']

            try:
                govuk_payment = payment_client.get_govuk_payment(govuk_id)
                previous_govuk_status = GovUkPaymentStatus.get_from_govuk_payment(
                    govuk_payment)
                govuk_status = payment_client.complete_payment_if_necessary(
                    payment, govuk_payment)

                # not yet finished and can't do anything so skip
                if govuk_status and not govuk_status.finished():
                    continue

                if previous_govuk_status != govuk_status:
                    # refresh govuk payment to get up-to-date fields (e.g. error codes)
                    govuk_payment = payment_client.get_govuk_payment(govuk_id)

                # if here, status is either success, failed, cancelled, error
                # or None (in case of govuk payment not found)
                payment_client.update_completed_payment(payment, govuk_payment)
            except OAuth2Error:
                logger.exception(
                    'Scheduled job: Authentication error while processing %s' %
                    payment_ref)
            except RequestException as error:
                error_message = 'Scheduled job: Payment check failed for ref %s' % payment_ref
                if hasattr(error, 'response') and hasattr(
                        error.response, 'content'):
                    error_message += '\nReceived: %s' % error.response.content
                logger.exception(error_message)
            except GovUkPaymentStatusException:
                # expected much of the time
                pass
    def get(self, request, *args, **kwargs):
        payment_ref = self.request.GET.get('payment_ref')
        if not payment_ref:
            return clear_session_view(request)
        kwargs['short_payment_ref'] = payment_ref[:8].upper()
        try:
            # check payment status
            payment_client = PaymentClient()
            payment = payment_client.get_payment(payment_ref)

            # only continue if:
            # - the MTP payment is in pending (it moves to the 'taken' state by the cronjob x mins after
            #   the gov.uk payment succeeds)
            #   OR
            # - the MTP payment is in the 'taken' state (by the cronjob x mins after the gov.uk payment succeeded)
            #   but only for a limited period of time
            if not payment or not is_active_payment(payment):
                return clear_session_view(request)

            kwargs.update({
                'prisoner_name': payment['recipient_name'],
                'prisoner_number': payment['prisoner_number'],
                'amount': decimal.Decimal(payment['amount']) / 100,
            })

            if payment['status'] == 'taken':
                self.status = GovUkPaymentStatus.success
            else:
                # check gov.uk payment status
                govuk_id = payment['processor_id']
                govuk_payment = payment_client.get_govuk_payment(govuk_id)

                self.status = payment_client.complete_payment_if_necessary(
                    payment, govuk_payment)

                # here status can be either created, started, submitted, capturable, success, failed, cancelled, error
                # or None

                error_code = govuk_payment and govuk_payment.get(
                    'state', {}).get('code')

                # payment was cancelled programmatically (this would not currently happen)
                if self.status == GovUkPaymentStatus.cancelled:
                    # error_code is expected to be P0040
                    error_code == 'P0040' or logger.error(
                        f'Unexpected code for cancelled GOV.UK Pay payment {payment_ref}: {error_code}'
                    )
                    return render(request,
                                  'send_money/debit-card-cancelled.html')

                # the user cancelled the payment
                if self.status == GovUkPaymentStatus.failed and error_code == 'P0030':
                    return render(request,
                                  'send_money/debit-card-cancelled.html')

                # GOV.UK Pay session expired
                if self.status == GovUkPaymentStatus.failed and error_code == 'P0020':
                    return render(
                        request, 'send_money/debit-card-session-expired.html')

                # payment method was rejected by card issuer or processor
                # e.g. due to insufficient funds or risk management
                if self.status == GovUkPaymentStatus.failed:
                    # error_code is expected to be P0010
                    error_code == 'P0010' or logger.error(
                        f'Unexpected code for failed GOV.UK Pay payment {payment_ref}: {error_code}'
                    )
                    return render(request,
                                  'send_money/debit-card-declined.html')

                # here status can be either created, started, submitted, capturable, success, error
                # or None

                # treat statuses created, started, submitted or None as error as they should have never got here
                if not self.status or self.status.is_awaiting_user_input():
                    self.status = GovUkPaymentStatus.error

                # here status can be either capturable, success, error

        except OAuth2Error:
            logger.exception(
                'Authentication error while processing %(payment_ref)s',
                {'payment_ref': payment_ref},
            )
            self.status = GovUkPaymentStatus.error
        except RequestException as error:
            response_content = get_requests_exception_for_logging(error)
            logger.exception(
                'Payment check failed for ref %(payment_ref)s. Received: %(response_content)s',
                {
                    'payment_ref': payment_ref,
                    'response_content': response_content
                },
            )
            self.status = GovUkPaymentStatus.error
        except GovUkPaymentStatusException:
            logger.exception(
                'GOV.UK Pay returned unexpected status for ref %(payment_ref)s',
                {'payment_ref': payment_ref},
            )
            self.status = GovUkPaymentStatus.error

        response = super().get(request, *args, **kwargs)
        request.session.flush()
        return response
Example #20
0
    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()
Example #21
0
    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'], '*****@*****.**')
Example #22
0
    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()