Coverage for benefits/in_person/views.py: 98%
114 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-10 16:52 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-10 16:52 +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, mixins
20from benefits.routes import routes
22logger = logging.getLogger(__name__)
25class EligibilityView(mixins.CommonContextMixin, 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_form_kwargs(self):
45 """Return the keyword arguments for instantiating the form."""
47 kwargs = super().get_form_kwargs()
48 kwargs["agency"] = self.agency
49 return kwargs
51 def form_valid(self, form):
52 """If the form is valid, set enrollment flow, eligible session, and redirect."""
54 flow_id = form.cleaned_data.get("flow")
55 flow = models.EnrollmentFlow.objects.get(id=flow_id)
56 session.update(self.request, flow=flow, eligible=True)
57 eligibility_analytics.selected_flow(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
58 eligibility_analytics.started_eligibility(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
59 eligibility_analytics.returned_success(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
60 return redirect(routes.IN_PERSON_ENROLLMENT)
63class LittlepayTokenView(TokenView):
64 """View handler for the enrollment auth token."""
66 enrollment_method = models.EnrollmentMethods.IN_PERSON
67 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
68 route_server_error = routes.IN_PERSON_SERVER_ERROR
71class EnrollmentView(IndexView):
73 route_origin = routes.IN_PERSON_ENROLLMENT
75 def get_redirect_url(self, *args, **kwargs):
76 route_name = self.agency.in_person_enrollment_index_route
77 return reverse(route_name)
80class LittlepayEnrollmentView(mixins.CommonContextMixin, LittlepayIndexView):
81 """View handler for the in-person enrollment page."""
83 enrollment_method = models.EnrollmentMethods.IN_PERSON
84 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
85 route_enrollment_retry = routes.IN_PERSON_ENROLLMENT_RETRY
86 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
87 route_server_error = routes.IN_PERSON_SERVER_ERROR
88 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
89 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_LITTLEPAY_INDEX
90 template_name = "in_person/enrollment/index_littlepay.html"
92 def _get_verified_by(self):
93 return f"{self.request.user.first_name} {self.request.user.last_name}"
96def reenrollment_error(request):
97 """View handler for a re-enrollment attempt that is not yet within the re-enrollment window."""
99 agency = session.agency(request)
100 context = {
101 **admin_site.each_context(request),
102 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
103 }
105 flow = session.flow(request)
106 context["flow_label"] = flow.label
108 return TemplateResponse(request, "in_person/enrollment/reenrollment_error.html", context)
111def retry(request):
112 """View handler for card verification failure."""
113 # enforce POST-only route for sending analytics
114 if request.method == "POST":
115 enrollment_analytics.returned_retry(request, enrollment_method=models.EnrollmentMethods.IN_PERSON)
117 agency = session.agency(request)
118 context = {
119 **admin_site.each_context(request),
120 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
121 }
123 return TemplateResponse(request, "in_person/enrollment/retry.html", context)
126def system_error(request):
127 """View handler for an enrollment system error."""
128 agency = session.agency(request)
129 context = {
130 **admin_site.each_context(request),
131 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
132 }
134 return TemplateResponse(request, "in_person/enrollment/system_error.html", context)
137def server_error(request):
138 """View handler for errors caused by a misconfiguration or bad request."""
139 agency = session.agency(request)
140 context = {
141 **admin_site.each_context(request),
142 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
143 }
145 return TemplateResponse(request, "in_person/enrollment/server_error.html", context)
148def success(request):
149 """View handler for the final success page."""
150 agency = session.agency(request)
151 context = {
152 **admin_site.each_context(request),
153 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
154 }
156 return TemplateResponse(request, "in_person/enrollment/success.html", context)
159class SwitchioGatewayUrlView(GatewayUrlView):
160 enrollment_method = models.EnrollmentMethods.IN_PERSON
161 route_redirect = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
162 route_server_error = routes.IN_PERSON_SERVER_ERROR
163 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
166class SwitchioEnrollmentIndexView(mixins.CommonContextMixin, SwitchioIndexView):
167 enrollment_method = models.EnrollmentMethods.IN_PERSON
168 form_class = forms.CardTokenizeSuccessForm
169 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
170 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
171 route_retry = routes.IN_PERSON_ENROLLMENT_RETRY
172 route_server_error = routes.IN_PERSON_SERVER_ERROR
173 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
174 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
175 template_name = "in_person/enrollment/index_switchio.html"
177 def _get_verified_by(self):
178 return f"{self.request.user.first_name} {self.request.user.last_name}"
180 def get_context_data(self, **kwargs):
181 """Add in-person specific context data."""
182 context = super().get_context_data(**kwargs)
184 if self.request.GET.get("state") == "tokenize":
185 message = "Registering this contactless card for reduced fares..."
186 else:
187 message = "Connecting with payment processor..."
189 context.update({"loading_message": message})
190 return context
192 def get(self, request, *args, **kwargs):
193 if request.GET.get("error") == "canceled": 193 ↛ 198line 193 didn't jump to line 198 because the condition on line 193 was always true
194 # the user clicked the "Back" button on the Switchio tokenization gateway
195 # send them back to the Admin index, similar to the Littlepay cancel button
196 return redirect(routes.ADMIN_INDEX)
198 return super().get(request, *args, **kwargs)