We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Enrollment page is accessible without passing eligibility verification
If a user knows the `/enrollment` endpoint, and has configured their session with a Transit Agency, they can navigate to the credit card enrollment page and completely bypass the EV check. This is a serious bug! 

## Steps to reproduce

1. Visit https://test-benefits.calitp.org
2. Click either of the Agency buttons
3. Manually update the URL to https://test-benefits.calitp.org/enrollment
4. Profit

## Remediation

This page must ensure the user has previously verified eligibility criteria

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `benefits/core/middleware.py`
Content:
```
1 """
2 The core application: middleware definitions for request/response cycle.
3 """
4 import logging
5 
6 from django.http import HttpResponse
7 from django.utils.decorators import decorator_from_middleware
8 from django.utils.deprecation import MiddlewareMixin
9 from django.views import i18n
10 
11 from benefits.settings import DEBUG
12 from . import analytics, session
13 
14 
15 logger = logging.getLogger(__name__)
16 
17 
18 class AgencySessionRequired(MiddlewareMixin):
19     """Middleware raises an exception for sessions lacking an agency configuration."""
20 
21     def process_request(self, request):
22         if session.active_agency(request):
23             logger.debug("Session configured with agency")
24             return None
25         else:
26             raise AttributeError("Session not configured with agency")
27 
28 
29 class DebugSession(MiddlewareMixin):
30     """Middleware to configure debug context in the request session."""
31 
32     def process_request(self, request):
33         session.update(request, debug=DEBUG)
34         return None
35 
36 
37 class Healthcheck:
38     """Middleware intercepts and accepts /healthcheck requests."""
39 
40     def __init__(self, get_response):
41         self.get_response = get_response
42 
43     def __call__(self, request):
44         if request.path == "/healthcheck":
45             return HttpResponse("Healthy", content_type="text/plain")
46         return self.get_response(request)
47 
48 
49 class ViewedPageEvent(MiddlewareMixin):
50     """Middleware sends an analytics event for page views."""
51 
52     def process_response(self, request, response):
53         event = analytics.ViewedPageEvent(request)
54         try:
55             analytics.send_event(event)
56         except Exception:
57             logger.warning(f"Failed to send event: {event}")
58         finally:
59             return response
60 
61 
62 pageview_decorator = decorator_from_middleware(ViewedPageEvent)
63 
64 
65 class ChangedLanguageEvent(MiddlewareMixin):
66     """Middleware hooks into django.views.i18n.set_language to send an analytics event."""
67 
68     def process_view(self, request, view_func, view_args, view_kwargs):
69         if view_func == i18n.set_language:
70             new_lang = request.POST["language"]
71             event = analytics.ChangedLanguageEvent(request, new_lang)
72             analytics.send_event(event)
73         return None
74
```

Path: `benefits/enrollment/views.py`
Content:
```
1 """
2 The enrollment application: view definitions for the benefits enrollment flow.
3 """
4 import logging
5 
6 from django.template.response import TemplateResponse
7 from django.urls import reverse
8 from django.utils.decorators import decorator_from_middleware
9 from django.utils.translation import pgettext, ugettext as _
10 
11 from benefits.core import middleware, models, session, viewmodels
12 from benefits.core.views import PageTemplateResponse
13 from . import api, forms
14 
15 
16 logger = logging.getLogger(__name__)
17 
18 
19 def _check_access_token(request, agency):
20     """
21     Ensure the request's session is configured with an access token.
22     """
23     if not session.valid_token(request):
24         response = api.Client(agency).access_token()
25         session.update(request, token=response.access_token, token_exp=response.expiry)
26 
27 
28 def _index(request):
29     """Helper handles GET requests to enrollment index."""
30     agency = session.agency(request)
31 
32     _check_access_token(request, agency)
33 
34     tokenize_button = "tokenize_card"
35     tokenize_retry_form = forms.CardTokenizeFailForm("enrollment:retry")
36     tokenize_success_form = forms.CardTokenizeSuccessForm(auto_id=True, label_suffix="")
37 
38     page = viewmodels.Page(
39         title=_("enrollment.index.title"),
40         content_title=_("enrollment.index.content_title"),
41         icon=viewmodels.Icon("idcardcheck", pgettext("image alt text", "core.icons.idcardcheck")),
42         paragraphs=[_("enrollment.index.p1"), _("enrollment.index.p2")],
43         classes="text-lg-center",
44         forms=[tokenize_retry_form, tokenize_success_form],
45         buttons=[
46             viewmodels.Button.primary(
47                 text=_("enrollment.buttons.paymentpartner"), id=tokenize_button, url=f"#{tokenize_button}"
48             ),
49             viewmodels.Button.link(
50                 classes="btn-sm", text=_("enrollment.buttons.paymentoptions"), url=reverse("core:payment_options")
51             ),
52         ],
53     )
54     context = {}
55     context.update(page.context_dict())
56 
57     # add agency details
58     agency_vm = viewmodels.TransitAgency(agency)
59     context.update(agency_vm.context_dict())
60 
61     # and payment processor details
62     processor_vm = viewmodels.PaymentProcessor(
63         model=agency.payment_processor,
64         access_token=session.token(request),
65         element_id=f"#{tokenize_button}",
66         color="#046b99",
67         name=f"{agency.long_name} {_('partnered with')} {agency.payment_processor.name}",
68     )
69     context.update(processor_vm.context_dict())
70     logger.warn(f"card_tokenize_url: {context['payment_processor'].card_tokenize_url}")
71 
72     # the tokenize form URLs are injected to page-generated Javascript
73     context["forms"] = {
74         "tokenize_retry": reverse(tokenize_retry_form.action_url),
75         "tokenize_success": reverse(tokenize_success_form.action_url),
76     }
77 
78     return TemplateResponse(request, "enrollment/index.html", context)
79 
80 
81 @decorator_from_middleware(middleware.AgencySessionRequired)
82 def index(request):
83     """View handler for the enrollment landing page."""
84     if request.method == "POST":
85         response = _enroll(request)
86     else:
87         response = _index(request)
88 
89     return response
90 
91 
92 def _enroll(request):
93     """Helper calls the enrollment APIs."""
94     logger.debug("Read tokenized card")
95     form = forms.CardTokenizeSuccessForm(request.POST)
96     if not form.is_valid():
97         raise Exception("Invalid card token form")
98     card_token = form.cleaned_data.get("card_token")
99 
100     eligibility = session.eligibility(request)
101     if eligibility:
102         logger.debug(f"Session contains an {models.EligibilityType.__name__}")
103     else:
104         raise Exception("Session contains no eligibility information")
105 
106     agency = session.agency(request)
107 
108     response = api.Client(agency).enroll(card_token, eligibility.group_id)
109 
110     if response.success:
111         return success(request)
112     else:
113         raise Exception("Updated customer_id does not match enrolled customer_id")
114 
115 
116 @decorator_from_middleware(middleware.AgencySessionRequired)
117 def retry(request):
118     """View handler for a recoverable failure condition."""
119     if request.method == "POST":
120         form = forms.CardTokenizeFailForm(request.POST)
121         if form.is_valid():
122             agency = session.agency(request)
123             page = viewmodels.Page(
124                 title=_("enrollment.retry.title"),
125                 icon=viewmodels.Icon("bankcardquestion", pgettext("image alt text", "core.icons.bankcardquestion")),
126                 content_title=_("enrollment.retry.title"),
127                 paragraphs=[_("enrollment.retry.p1")],
128                 buttons=viewmodels.Button.agency_contact_links(agency),
129             )
130             page.buttons.append(viewmodels.Button.primary(text=_("enrollment.retry.button"), url=session.origin(request)))
131             return PageTemplateResponse(request, page)
132         else:
133             raise Exception("Invalid retry submission.")
134     else:
135         raise Exception("This view method only supports POST.")
136 
137 
138 @middleware.pageview_decorator
139 def success(request):
140     """View handler for the final success page."""
141     request.path = "/enrollment/success"
142 
143     page = viewmodels.Page(
144         title=_("enrollment.success.title"),
145         icon=viewmodels.Icon("bankcardcheck", pgettext("image alt text", "core.icons.bankcardcheck")),
146         content_title=_("enrollment.success.title"),
147         paragraphs=[_("enrollment.success.p1"), _("enrollment.success.p2")],
148     )
149 
150     return TemplateResponse(request, "enrollment/success.html", page.context_dict())
151
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/benefits/core/middleware.py b/benefits/core/middleware.py
--- a/benefits/core/middleware.py
+++ b/benefits/core/middleware.py
@@ -26,6 +26,17 @@
             raise AttributeError("Session not configured with agency")
 
 
+class EligibleSessionRequired(MiddlewareMixin):
+    """Middleware raises an exception for sessions lacking confirmed eligibility."""
+
+    def process_request(self, request):
+        if session.eligible(request):
+            logger.debug("Session has confirmed eligibility")
+            return None
+        else:
+            raise AttributeError("Session has no confirmed eligibility")
+
+
 class DebugSession(MiddlewareMixin):
     """Middleware to configure debug context in the request session."""
 
diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py
--- a/benefits/enrollment/views.py
+++ b/benefits/enrollment/views.py
@@ -78,17 +78,6 @@
     return TemplateResponse(request, "enrollment/index.html", context)
 
 
-@decorator_from_middleware(middleware.AgencySessionRequired)
-def index(request):
-    """View handler for the enrollment landing page."""
-    if request.method == "POST":
-        response = _enroll(request)
-    else:
-        response = _index(request)
-
-    return response
-
-
 def _enroll(request):
     """Helper calls the enrollment APIs."""
     logger.debug("Read tokenized card")
@@ -113,7 +102,18 @@
         raise Exception("Updated customer_id does not match enrolled customer_id")
 
 
-@decorator_from_middleware(middleware.AgencySessionRequired)
+@decorator_from_middleware(middleware.EligibleSessionRequired)
+def index(request):
+    """View handler for the enrollment landing page."""
+    if request.method == "POST":
+        response = _enroll(request)
+    else:
+        response = _index(request)
+
+    return response
+
+
+@decorator_from_middleware(middleware.EligibleSessionRequired)
 def retry(request):
     """View handler for a recoverable failure condition."""
     if request.method == "POST":

{"golden_diff": "diff --git a/benefits/core/middleware.py b/benefits/core/middleware.py\n--- a/benefits/core/middleware.py\n+++ b/benefits/core/middleware.py\n@@ -26,6 +26,17 @@\n             raise AttributeError(\"Session not configured with agency\")\n \n \n+class EligibleSessionRequired(MiddlewareMixin):\n+    \"\"\"Middleware raises an exception for sessions lacking confirmed eligibility.\"\"\"\n+\n+    def process_request(self, request):\n+        if session.eligible(request):\n+            logger.debug(\"Session has confirmed eligibility\")\n+            return None\n+        else:\n+            raise AttributeError(\"Session has no confirmed eligibility\")\n+\n+\n class DebugSession(MiddlewareMixin):\n     \"\"\"Middleware to configure debug context in the request session.\"\"\"\n \ndiff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py\n--- a/benefits/enrollment/views.py\n+++ b/benefits/enrollment/views.py\n@@ -78,17 +78,6 @@\n     return TemplateResponse(request, \"enrollment/index.html\", context)\n \n \n-@decorator_from_middleware(middleware.AgencySessionRequired)\n-def index(request):\n-    \"\"\"View handler for the enrollment landing page.\"\"\"\n-    if request.method == \"POST\":\n-        response = _enroll(request)\n-    else:\n-        response = _index(request)\n-\n-    return response\n-\n-\n def _enroll(request):\n     \"\"\"Helper calls the enrollment APIs.\"\"\"\n     logger.debug(\"Read tokenized card\")\n@@ -113,7 +102,18 @@\n         raise Exception(\"Updated customer_id does not match enrolled customer_id\")\n \n \n-@decorator_from_middleware(middleware.AgencySessionRequired)\n+@decorator_from_middleware(middleware.EligibleSessionRequired)\n+def index(request):\n+    \"\"\"View handler for the enrollment landing page.\"\"\"\n+    if request.method == \"POST\":\n+        response = _enroll(request)\n+    else:\n+        response = _index(request)\n+\n+    return response\n+\n+\n+@decorator_from_middleware(middleware.EligibleSessionRequired)\n def retry(request):\n     \"\"\"View handler for a recoverable failure condition.\"\"\"\n     if request.method == \"POST\":\n", "issue": "Enrollment page is accessible without passing eligibility verification\nIf a user knows the `/enrollment` endpoint, and has configured their session with a Transit Agency, they can navigate to the credit card enrollment page and completely bypass the EV check. This is a serious bug! \r\n\r\n## Steps to reproduce\r\n\r\n1. Visit https://test-benefits.calitp.org\r\n2. Click either of the Agency buttons\r\n3. Manually update the URL to https://test-benefits.calitp.org/enrollment\r\n4. Profit\r\n\r\n## Remediation\r\n\r\nThis page must ensure the user has previously verified eligibility criteria\n", "before_files": [{"content": "\"\"\"\nThe core application: middleware definitions for request/response cycle.\n\"\"\"\nimport logging\n\nfrom django.http import HttpResponse\nfrom django.utils.decorators import decorator_from_middleware\nfrom django.utils.deprecation import MiddlewareMixin\nfrom django.views import i18n\n\nfrom benefits.settings import DEBUG\nfrom . import analytics, session\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgencySessionRequired(MiddlewareMixin):\n    \"\"\"Middleware raises an exception for sessions lacking an agency configuration.\"\"\"\n\n    def process_request(self, request):\n        if session.active_agency(request):\n            logger.debug(\"Session configured with agency\")\n            return None\n        else:\n            raise AttributeError(\"Session not configured with agency\")\n\n\nclass DebugSession(MiddlewareMixin):\n    \"\"\"Middleware to configure debug context in the request session.\"\"\"\n\n    def process_request(self, request):\n        session.update(request, debug=DEBUG)\n        return None\n\n\nclass Healthcheck:\n    \"\"\"Middleware intercepts and accepts /healthcheck requests.\"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        if request.path == \"/healthcheck\":\n            return HttpResponse(\"Healthy\", content_type=\"text/plain\")\n        return self.get_response(request)\n\n\nclass ViewedPageEvent(MiddlewareMixin):\n    \"\"\"Middleware sends an analytics event for page views.\"\"\"\n\n    def process_response(self, request, response):\n        event = analytics.ViewedPageEvent(request)\n        try:\n            analytics.send_event(event)\n        except Exception:\n            logger.warning(f\"Failed to send event: {event}\")\n        finally:\n            return response\n\n\npageview_decorator = decorator_from_middleware(ViewedPageEvent)\n\n\nclass ChangedLanguageEvent(MiddlewareMixin):\n    \"\"\"Middleware hooks into django.views.i18n.set_language to send an analytics event.\"\"\"\n\n    def process_view(self, request, view_func, view_args, view_kwargs):\n        if view_func == i18n.set_language:\n            new_lang = request.POST[\"language\"]\n            event = analytics.ChangedLanguageEvent(request, new_lang)\n            analytics.send_event(event)\n        return None\n", "path": "benefits/core/middleware.py"}, {"content": "\"\"\"\nThe enrollment application: view definitions for the benefits enrollment flow.\n\"\"\"\nimport logging\n\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse\nfrom django.utils.decorators import decorator_from_middleware\nfrom django.utils.translation import pgettext, ugettext as _\n\nfrom benefits.core import middleware, models, session, viewmodels\nfrom benefits.core.views import PageTemplateResponse\nfrom . import api, forms\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef _check_access_token(request, agency):\n    \"\"\"\n    Ensure the request's session is configured with an access token.\n    \"\"\"\n    if not session.valid_token(request):\n        response = api.Client(agency).access_token()\n        session.update(request, token=response.access_token, token_exp=response.expiry)\n\n\ndef _index(request):\n    \"\"\"Helper handles GET requests to enrollment index.\"\"\"\n    agency = session.agency(request)\n\n    _check_access_token(request, agency)\n\n    tokenize_button = \"tokenize_card\"\n    tokenize_retry_form = forms.CardTokenizeFailForm(\"enrollment:retry\")\n    tokenize_success_form = forms.CardTokenizeSuccessForm(auto_id=True, label_suffix=\"\")\n\n    page = viewmodels.Page(\n        title=_(\"enrollment.index.title\"),\n        content_title=_(\"enrollment.index.content_title\"),\n        icon=viewmodels.Icon(\"idcardcheck\", pgettext(\"image alt text\", \"core.icons.idcardcheck\")),\n        paragraphs=[_(\"enrollment.index.p1\"), _(\"enrollment.index.p2\")],\n        classes=\"text-lg-center\",\n        forms=[tokenize_retry_form, tokenize_success_form],\n        buttons=[\n            viewmodels.Button.primary(\n                text=_(\"enrollment.buttons.paymentpartner\"), id=tokenize_button, url=f\"#{tokenize_button}\"\n            ),\n            viewmodels.Button.link(\n                classes=\"btn-sm\", text=_(\"enrollment.buttons.paymentoptions\"), url=reverse(\"core:payment_options\")\n            ),\n        ],\n    )\n    context = {}\n    context.update(page.context_dict())\n\n    # add agency details\n    agency_vm = viewmodels.TransitAgency(agency)\n    context.update(agency_vm.context_dict())\n\n    # and payment processor details\n    processor_vm = viewmodels.PaymentProcessor(\n        model=agency.payment_processor,\n        access_token=session.token(request),\n        element_id=f\"#{tokenize_button}\",\n        color=\"#046b99\",\n        name=f\"{agency.long_name} {_('partnered with')} {agency.payment_processor.name}\",\n    )\n    context.update(processor_vm.context_dict())\n    logger.warn(f\"card_tokenize_url: {context['payment_processor'].card_tokenize_url}\")\n\n    # the tokenize form URLs are injected to page-generated Javascript\n    context[\"forms\"] = {\n        \"tokenize_retry\": reverse(tokenize_retry_form.action_url),\n        \"tokenize_success\": reverse(tokenize_success_form.action_url),\n    }\n\n    return TemplateResponse(request, \"enrollment/index.html\", context)\n\n\n@decorator_from_middleware(middleware.AgencySessionRequired)\ndef index(request):\n    \"\"\"View handler for the enrollment landing page.\"\"\"\n    if request.method == \"POST\":\n        response = _enroll(request)\n    else:\n        response = _index(request)\n\n    return response\n\n\ndef _enroll(request):\n    \"\"\"Helper calls the enrollment APIs.\"\"\"\n    logger.debug(\"Read tokenized card\")\n    form = forms.CardTokenizeSuccessForm(request.POST)\n    if not form.is_valid():\n        raise Exception(\"Invalid card token form\")\n    card_token = form.cleaned_data.get(\"card_token\")\n\n    eligibility = session.eligibility(request)\n    if eligibility:\n        logger.debug(f\"Session contains an {models.EligibilityType.__name__}\")\n    else:\n        raise Exception(\"Session contains no eligibility information\")\n\n    agency = session.agency(request)\n\n    response = api.Client(agency).enroll(card_token, eligibility.group_id)\n\n    if response.success:\n        return success(request)\n    else:\n        raise Exception(\"Updated customer_id does not match enrolled customer_id\")\n\n\n@decorator_from_middleware(middleware.AgencySessionRequired)\ndef retry(request):\n    \"\"\"View handler for a recoverable failure condition.\"\"\"\n    if request.method == \"POST\":\n        form = forms.CardTokenizeFailForm(request.POST)\n        if form.is_valid():\n            agency = session.agency(request)\n            page = viewmodels.Page(\n                title=_(\"enrollment.retry.title\"),\n                icon=viewmodels.Icon(\"bankcardquestion\", pgettext(\"image alt text\", \"core.icons.bankcardquestion\")),\n                content_title=_(\"enrollment.retry.title\"),\n                paragraphs=[_(\"enrollment.retry.p1\")],\n                buttons=viewmodels.Button.agency_contact_links(agency),\n            )\n            page.buttons.append(viewmodels.Button.primary(text=_(\"enrollment.retry.button\"), url=session.origin(request)))\n            return PageTemplateResponse(request, page)\n        else:\n            raise Exception(\"Invalid retry submission.\")\n    else:\n        raise Exception(\"This view method only supports POST.\")\n\n\n@middleware.pageview_decorator\ndef success(request):\n    \"\"\"View handler for the final success page.\"\"\"\n    request.path = \"/enrollment/success\"\n\n    page = viewmodels.Page(\n        title=_(\"enrollment.success.title\"),\n        icon=viewmodels.Icon(\"bankcardcheck\", pgettext(\"image alt text\", \"core.icons.bankcardcheck\")),\n        content_title=_(\"enrollment.success.title\"),\n        paragraphs=[_(\"enrollment.success.p1\"), _(\"enrollment.success.p2\")],\n    )\n\n    return TemplateResponse(request, \"enrollment/success.html\", page.context_dict())\n", "path": "benefits/enrollment/views.py"}], "after_files": [{"content": "\"\"\"\nThe core application: middleware definitions for request/response cycle.\n\"\"\"\nimport logging\n\nfrom django.http import HttpResponse\nfrom django.utils.decorators import decorator_from_middleware\nfrom django.utils.deprecation import MiddlewareMixin\nfrom django.views import i18n\n\nfrom benefits.settings import DEBUG\nfrom . import analytics, session\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgencySessionRequired(MiddlewareMixin):\n    \"\"\"Middleware raises an exception for sessions lacking an agency configuration.\"\"\"\n\n    def process_request(self, request):\n        if session.active_agency(request):\n            logger.debug(\"Session configured with agency\")\n            return None\n        else:\n            raise AttributeError(\"Session not configured with agency\")\n\n\nclass EligibleSessionRequired(MiddlewareMixin):\n    \"\"\"Middleware raises an exception for sessions lacking confirmed eligibility.\"\"\"\n\n    def process_request(self, request):\n        if session.eligible(request):\n            logger.debug(\"Session has confirmed eligibility\")\n            return None\n        else:\n            raise AttributeError(\"Session has no confirmed eligibility\")\n\n\nclass DebugSession(MiddlewareMixin):\n    \"\"\"Middleware to configure debug context in the request session.\"\"\"\n\n    def process_request(self, request):\n        session.update(request, debug=DEBUG)\n        return None\n\n\nclass Healthcheck:\n    \"\"\"Middleware intercepts and accepts /healthcheck requests.\"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        if request.path == \"/healthcheck\":\n            return HttpResponse(\"Healthy\", content_type=\"text/plain\")\n        return self.get_response(request)\n\n\nclass ViewedPageEvent(MiddlewareMixin):\n    \"\"\"Middleware sends an analytics event for page views.\"\"\"\n\n    def process_response(self, request, response):\n        event = analytics.ViewedPageEvent(request)\n        try:\n            analytics.send_event(event)\n        except Exception:\n            logger.warning(f\"Failed to send event: {event}\")\n        finally:\n            return response\n\n\npageview_decorator = decorator_from_middleware(ViewedPageEvent)\n\n\nclass ChangedLanguageEvent(MiddlewareMixin):\n    \"\"\"Middleware hooks into django.views.i18n.set_language to send an analytics event.\"\"\"\n\n    def process_view(self, request, view_func, view_args, view_kwargs):\n        if view_func == i18n.set_language:\n            new_lang = request.POST[\"language\"]\n            event = analytics.ChangedLanguageEvent(request, new_lang)\n            analytics.send_event(event)\n        return None\n", "path": "benefits/core/middleware.py"}, {"content": "\"\"\"\nThe enrollment application: view definitions for the benefits enrollment flow.\n\"\"\"\nimport logging\n\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse\nfrom django.utils.decorators import decorator_from_middleware\nfrom django.utils.translation import pgettext, ugettext as _\n\nfrom benefits.core import middleware, models, session, viewmodels\nfrom benefits.core.views import PageTemplateResponse\nfrom . import api, forms\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef _check_access_token(request, agency):\n    \"\"\"\n    Ensure the request's session is configured with an access token.\n    \"\"\"\n    if not session.valid_token(request):\n        response = api.Client(agency).access_token()\n        session.update(request, token=response.access_token, token_exp=response.expiry)\n\n\ndef _index(request):\n    \"\"\"Helper handles GET requests to enrollment index.\"\"\"\n    agency = session.agency(request)\n\n    _check_access_token(request, agency)\n\n    tokenize_button = \"tokenize_card\"\n    tokenize_retry_form = forms.CardTokenizeFailForm(\"enrollment:retry\")\n    tokenize_success_form = forms.CardTokenizeSuccessForm(auto_id=True, label_suffix=\"\")\n\n    page = viewmodels.Page(\n        title=_(\"enrollment.index.title\"),\n        content_title=_(\"enrollment.index.content_title\"),\n        icon=viewmodels.Icon(\"idcardcheck\", pgettext(\"image alt text\", \"core.icons.idcardcheck\")),\n        paragraphs=[_(\"enrollment.index.p1\"), _(\"enrollment.index.p2\")],\n        classes=\"text-lg-center\",\n        forms=[tokenize_retry_form, tokenize_success_form],\n        buttons=[\n            viewmodels.Button.primary(\n                text=_(\"enrollment.buttons.paymentpartner\"), id=tokenize_button, url=f\"#{tokenize_button}\"\n            ),\n            viewmodels.Button.link(\n                classes=\"btn-sm\", text=_(\"enrollment.buttons.paymentoptions\"), url=reverse(\"core:payment_options\")\n            ),\n        ],\n    )\n    context = {}\n    context.update(page.context_dict())\n\n    # add agency details\n    agency_vm = viewmodels.TransitAgency(agency)\n    context.update(agency_vm.context_dict())\n\n    # and payment processor details\n    processor_vm = viewmodels.PaymentProcessor(\n        model=agency.payment_processor,\n        access_token=session.token(request),\n        element_id=f\"#{tokenize_button}\",\n        color=\"#046b99\",\n        name=f\"{agency.long_name} {_('partnered with')} {agency.payment_processor.name}\",\n    )\n    context.update(processor_vm.context_dict())\n    logger.warn(f\"card_tokenize_url: {context['payment_processor'].card_tokenize_url}\")\n\n    # the tokenize form URLs are injected to page-generated Javascript\n    context[\"forms\"] = {\n        \"tokenize_retry\": reverse(tokenize_retry_form.action_url),\n        \"tokenize_success\": reverse(tokenize_success_form.action_url),\n    }\n\n    return TemplateResponse(request, \"enrollment/index.html\", context)\n\n\ndef _enroll(request):\n    \"\"\"Helper calls the enrollment APIs.\"\"\"\n    logger.debug(\"Read tokenized card\")\n    form = forms.CardTokenizeSuccessForm(request.POST)\n    if not form.is_valid():\n        raise Exception(\"Invalid card token form\")\n    card_token = form.cleaned_data.get(\"card_token\")\n\n    eligibility = session.eligibility(request)\n    if eligibility:\n        logger.debug(f\"Session contains an {models.EligibilityType.__name__}\")\n    else:\n        raise Exception(\"Session contains no eligibility information\")\n\n    agency = session.agency(request)\n\n    response = api.Client(agency).enroll(card_token, eligibility.group_id)\n\n    if response.success:\n        return success(request)\n    else:\n        raise Exception(\"Updated customer_id does not match enrolled customer_id\")\n\n\n@decorator_from_middleware(middleware.EligibleSessionRequired)\ndef index(request):\n    \"\"\"View handler for the enrollment landing page.\"\"\"\n    if request.method == \"POST\":\n        response = _enroll(request)\n    else:\n        response = _index(request)\n\n    return response\n\n\n@decorator_from_middleware(middleware.EligibleSessionRequired)\ndef retry(request):\n    \"\"\"View handler for a recoverable failure condition.\"\"\"\n    if request.method == \"POST\":\n        form = forms.CardTokenizeFailForm(request.POST)\n        if form.is_valid():\n            agency = session.agency(request)\n            page = viewmodels.Page(\n                title=_(\"enrollment.retry.title\"),\n                icon=viewmodels.Icon(\"bankcardquestion\", pgettext(\"image alt text\", \"core.icons.bankcardquestion\")),\n                content_title=_(\"enrollment.retry.title\"),\n                paragraphs=[_(\"enrollment.retry.p1\")],\n                buttons=viewmodels.Button.agency_contact_links(agency),\n            )\n            page.buttons.append(viewmodels.Button.primary(text=_(\"enrollment.retry.button\"), url=session.origin(request)))\n            return PageTemplateResponse(request, page)\n        else:\n            raise Exception(\"Invalid retry submission.\")\n    else:\n        raise Exception(\"This view method only supports POST.\")\n\n\n@middleware.pageview_decorator\ndef success(request):\n    \"\"\"View handler for the final success page.\"\"\"\n    request.path = \"/enrollment/success\"\n\n    page = viewmodels.Page(\n        title=_(\"enrollment.success.title\"),\n        icon=viewmodels.Icon(\"bankcardcheck\", pgettext(\"image alt text\", \"core.icons.bankcardcheck\")),\n        content_title=_(\"enrollment.success.title\"),\n        paragraphs=[_(\"enrollment.success.p1\"), _(\"enrollment.success.p2\")],\n    )\n\n    return TemplateResponse(request, \"enrollment/success.html\", page.context_dict())\n", "path": "benefits/enrollment/views.py"}]}