Coverage for benefits/in_person/views.py: 98%
122 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-12 23:32 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-12 23:32 +0000
1import logging
3from django.contrib.admin import site as admin_site
4from django.shortcuts import redirect
5from django.template.response import TemplateResponse
6from django.urls import reverse
7from django.views.generic import FormView
9from benefits.core.models.transit import TransitAgency
10from benefits.core import models, session
11from benefits.eligibility import analytics as eligibility_analytics
12from benefits.enrollment import analytics as enrollment_analytics
13from benefits.enrollment.views import IndexView
14from benefits.enrollment_littlepay.session import Session as LittlepaySession
15from benefits.enrollment_littlepay.views import TokenView, IndexView as LittlepayIndexView
16from benefits.enrollment_switchio.session import Session as SwitchioSession
17from benefits.enrollment_switchio.views import GatewayUrlView, IndexView as SwitchioIndexView
19from benefits.in_person import forms
20from benefits.routes import routes
22logger = logging.getLogger(__name__)
25class EligibilityView(FormView):
26 """CBV for the in-person eligibility flow selection form."""
28 template_name = "in_person/eligibility.html"
29 form_class = forms.InPersonEligibilityForm
31 def dispatch(self, request, *args, **kwargs):
32 """Initialize session state before handling the request."""
34 LittlepaySession(request, reset=True)
35 SwitchioSession(request, reset=True)
37 agency = session.agency(request)
38 if not agency:
39 agency = TransitAgency.for_user(request.user)
40 session.update(request, agency=agency)
41 self.agency = agency
42 return super().dispatch(request, *args, **kwargs)
44 def get_context_data(self, **kwargs):
45 """Add in-person specific context data."""
47 context = super().get_context_data(**kwargs)
48 context.update(
49 {
50 **admin_site.each_context(self.request),
51 "title": f"{self.agency.long_name} | In-person enrollment | {admin_site.site_title}",
52 }
53 )
54 return context
56 def get_form_kwargs(self):
57 """Return the keyword arguments for instantiating the form."""
59 kwargs = super().get_form_kwargs()
60 kwargs["agency"] = self.agency
61 return kwargs
63 def form_valid(self, form):
64 """If the form is valid, set enrollment flow, eligible session, and redirect."""
66 flow_id = form.cleaned_data.get("flow")
67 flow = models.EnrollmentFlow.objects.get(id=flow_id)
68 session.update(self.request, flow=flow, eligible=True)
69 eligibility_analytics.selected_flow(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
70 eligibility_analytics.started_eligibility(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
71 eligibility_analytics.returned_success(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
72 return redirect(routes.IN_PERSON_ENROLLMENT)
75class LittlepayTokenView(TokenView):
76 """View handler for the enrollment auth token."""
78 enrollment_method = models.EnrollmentMethods.IN_PERSON
79 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
80 route_server_error = routes.IN_PERSON_SERVER_ERROR
83class EnrollmentView(IndexView):
85 route_origin = routes.IN_PERSON_ENROLLMENT
87 def get_redirect_url(self, *args, **kwargs):
88 route_name = self.agency.in_person_enrollment_index_route
89 return reverse(route_name)
92class LittlepayEnrollmentView(LittlepayIndexView):
93 """View handler for the in-person enrollment page."""
95 enrollment_method = models.EnrollmentMethods.IN_PERSON
96 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
97 route_enrollment_retry = routes.IN_PERSON_ENROLLMENT_RETRY
98 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
99 route_server_error = routes.IN_PERSON_SERVER_ERROR
100 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
101 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_LITTLEPAY_INDEX
102 template_name = "in_person/enrollment/index_littlepay.html"
104 def _get_verified_by(self):
105 return f"{self.request.user.first_name} {self.request.user.last_name}"
107 def get_context_data(self, **kwargs):
108 """Add in-person specific context data."""
110 context = super().get_context_data(**kwargs)
111 context.update(
112 {
113 "title": f"{self.agency.long_name} | In-person enrollment | {admin_site.site_title}",
114 }
115 )
116 return context
119def reenrollment_error(request):
120 """View handler for a re-enrollment attempt that is not yet within the re-enrollment window."""
122 agency = session.agency(request)
123 context = {
124 **admin_site.each_context(request),
125 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
126 }
128 flow = session.flow(request)
129 context["flow_label"] = flow.label
131 return TemplateResponse(request, "in_person/enrollment/reenrollment_error.html", context)
134def retry(request):
135 """View handler for card verification failure."""
136 # enforce POST-only route for sending analytics
137 if request.method == "POST":
138 enrollment_analytics.returned_retry(request, enrollment_method=models.EnrollmentMethods.IN_PERSON)
140 agency = session.agency(request)
141 context = {
142 **admin_site.each_context(request),
143 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
144 }
146 return TemplateResponse(request, "in_person/enrollment/retry.html", context)
149def system_error(request):
150 """View handler for an enrollment system error."""
151 agency = session.agency(request)
152 context = {
153 **admin_site.each_context(request),
154 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
155 }
157 return TemplateResponse(request, "in_person/enrollment/system_error.html", context)
160def server_error(request):
161 """View handler for errors caused by a misconfiguration or bad request."""
162 agency = session.agency(request)
163 context = {
164 **admin_site.each_context(request),
165 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
166 }
168 return TemplateResponse(request, "in_person/enrollment/server_error.html", context)
171def success(request):
172 """View handler for the final success page."""
173 agency = session.agency(request)
174 context = {
175 **admin_site.each_context(request),
176 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
177 }
179 return TemplateResponse(request, "in_person/enrollment/success.html", context)
182class SwitchioGatewayUrlView(GatewayUrlView):
183 enrollment_method = models.EnrollmentMethods.IN_PERSON
184 route_redirect = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
185 route_server_error = routes.IN_PERSON_SERVER_ERROR
186 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
189class SwitchioEnrollmentIndexView(SwitchioIndexView):
190 enrollment_method = models.EnrollmentMethods.IN_PERSON
191 form_class = forms.CardTokenizeSuccessForm
192 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
193 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
194 route_retry = routes.IN_PERSON_ENROLLMENT_RETRY
195 route_server_error = routes.IN_PERSON_SERVER_ERROR
196 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
197 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
198 template_name = "in_person/enrollment/index_switchio.html"
200 def _get_verified_by(self):
201 return f"{self.request.user.first_name} {self.request.user.last_name}"
203 def get_context_data(self, **kwargs):
204 """Add in-person specific context data."""
205 context = super().get_context_data(**kwargs)
207 if self.request.GET.get("state") == "tokenize":
208 message = "Registering this contactless card for reduced fares..."
209 else:
210 message = "Connecting with payment processor..."
212 context.update(
213 {
214 "loading_message": message,
215 "title": f"{self.agency.long_name} | In-person enrollment | {admin_site.site_title}",
216 }
217 )
218 return context
220 def get(self, request, *args, **kwargs):
221 if request.GET.get("error") == "canceled": 221 ↛ 226line 221 didn't jump to line 226 because the condition on line 221 was always true
222 # the user clicked the "Back" button on the Switchio tokenization gateway
223 # send them back to the Admin index, similar to the Littlepay cancel button
224 return redirect(routes.ADMIN_INDEX)
226 return super().get(request, *args, **kwargs)