diff --git a/benefits/enrollment/templates/enrollment/index.html b/benefits/enrollment/templates/enrollment/index.html index f75e42303..0a4d8c318 100644 --- a/benefits/enrollment/templates/enrollment/index.html +++ b/benefits/enrollment/templates/enrollment/index.html @@ -41,6 +41,12 @@

$.ajax({ dataType: "script", attrs: { nonce: "{{ request.csp_nonce }}"}, url: "{{ card_tokenize_url }}" }) .done(function() { $.get("{{ access_token_url }}", function(data) { + if (data.redirect) { + // https://stackoverflow.com/a/42469170 + // use 'assign' because 'replace' was giving strange Back button behavior + window.location.assign(data.redirect); + } + $(".loading").remove(); // remove invisible and add back visible, so we aren't left with // a div with an empty class attribute diff --git a/benefits/enrollment/urls.py b/benefits/enrollment/urls.py index f74eb2452..fb3ddd5c1 100644 --- a/benefits/enrollment/urls.py +++ b/benefits/enrollment/urls.py @@ -15,4 +15,5 @@ path("reenrollment-error", views.reenrollment_error, name="reenrollment-error"), path("retry", views.retry, name="retry"), path("success", views.success, name="success"), + path("error", views.system_error, name="system-error"), ] diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index ee81973ab..7592abeae 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -22,6 +22,7 @@ ROUTE_REENROLLMENT_ERROR = "enrollment:reenrollment-error" ROUTE_RETRY = "enrollment:retry" ROUTE_SUCCESS = "enrollment:success" +ROUTE_SYSTEM_ERROR = "enrollment:system-error" ROUTE_TOKEN = "enrollment:token" TEMPLATE_RETRY = "enrollment/retry.html" @@ -44,8 +45,20 @@ def token(request): audience=payment_processor.audience, ) client.oauth.ensure_active_token(client.token) - response = client.request_card_tokenization_access() - session.update(request, enrollment_token=response.get("access_token"), enrollment_token_exp=response.get("expires_at")) + + try: + response = client.request_card_tokenization_access() + except Exception as e: + if isinstance(e, HTTPError) and e.response.status_code >= 500: + sentry_sdk.capture_exception(e) + data = {"redirect": reverse(ROUTE_SYSTEM_ERROR)} + return JsonResponse(data) + else: + raise e + else: + session.update( + request, enrollment_token=response.get("access_token"), enrollment_token_exp=response.get("expires_at") + ) data = {"token": session.enrollment_token(request)} diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index 49e25c708..231ba31fa 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -13,6 +13,7 @@ ROUTE_REENROLLMENT_ERROR, ROUTE_RETRY, ROUTE_SUCCESS, + ROUTE_SYSTEM_ERROR, ROUTE_TOKEN, TEMPLATE_SYSTEM_ERROR, TEMPLATE_RETRY, @@ -102,6 +103,31 @@ def test_token_valid(mocker, client): assert data["token"] == "enrollment_token" +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility") +def test_token_http_error_500(mocker, client): + mocker.patch("benefits.core.session.enrollment_token_valid", return_value=False) + + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + + mock_error = {"message": "Mock error message"} + mock_error_response = mocker.Mock(status_code=500, **mock_error) + mock_error_response.json.return_value = mock_error + mock_client.request_card_tokenization_access.side_effect = HTTPError( + response=mock_error_response, + ) + + path = reverse(ROUTE_TOKEN) + response = client.get(path) + + assert response.status_code == 200 + data = response.json() + assert "token" not in data + assert "redirect" in data + assert data["redirect"] == reverse(ROUTE_SYSTEM_ERROR) + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") def test_index_eligible_get(client, model_EligibilityType):