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):