Coverage for benefits/in_person/views.py: 98%
120 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-08 19:50 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-08 19:50 +0000
1import logging
3from django.shortcuts import redirect
4from django.urls import reverse
5from django.views.generic import FormView, TemplateView
7from benefits.core import models, session
8from benefits.core.mixins import AgencySessionRequiredMixin
9from benefits.core.models.transit import TransitAgency
10from benefits.core.views import AdditionalAgenciesView as SelfServiceAdditionalAgenciesView
11from benefits.eligibility import analytics as eligibility_analytics
12from benefits.enrollment.views import (
13 IndexView as SelfServiceEnrollmentIndexView,
14 ReenrollmentErrorView as SelfServiceReenrollmentErrorView,
15 RetryView as SelfServiceRetryView,
16 SuccessView as SelfServiceSuccessView,
17 SystemErrorView as SelfServiceSystemErrorView,
18)
19from benefits.enrollment_littlepay.routes import routes as littlepay_routes
20from benefits.enrollment_littlepay.session import Session as LittlepaySession
21from benefits.enrollment_littlepay.views import IndexView as LittlepayIndexView, TokenView
22from benefits.enrollment_switchio.routes import routes as switchio_routes
23from benefits.enrollment_switchio.session import Session as SwitchioSession
24from benefits.enrollment_switchio.views import GatewayUrlView, IndexView as SwitchioIndexView
25from benefits.in_person import forms, mixins
26from benefits.routes import routes
28logger = logging.getLogger(__name__)
31class AdditionalAgenciesView(mixins.CommonContextMixin, SelfServiceAdditionalAgenciesView):
32 """View handler for showing the list of agencies the customer will be enrolled at (if more than one)."""
34 template_name = "in_person/additional-agencies.html"
35 only_active_agencies = False
38class EligibilityView(mixins.CommonContextMixin, FormView):
39 """CBV for the in-person eligibility flow selection form."""
41 template_name = "in_person/eligibility.html"
42 form_class = forms.InPersonEligibilityForm
44 def dispatch(self, request, *args, **kwargs):
45 """Initialize session state before handling the request."""
47 LittlepaySession(request, reset=True)
48 SwitchioSession(request, reset=True)
50 agency = session.agency(request)
51 if not agency:
52 agency = TransitAgency.for_user(request.user)
53 session.update(request, agency=agency)
54 self.agency = agency
55 return super().dispatch(request, *args, **kwargs)
57 def get_form_kwargs(self):
58 """Return the keyword arguments for instantiating the form."""
60 kwargs = super().get_form_kwargs()
61 kwargs["agency"] = self.agency
62 return kwargs
64 def form_valid(self, form):
65 """If the form is valid, set enrollment flow, eligible session, and redirect."""
67 flow_id = form.cleaned_data.get("flow")
68 flow = models.EnrollmentFlow.objects.get(id=flow_id)
69 session.update(self.request, flow=flow, eligible=True)
70 eligibility_analytics.selected_flow(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
71 eligibility_analytics.started_eligibility(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
72 eligibility_analytics.returned_success(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
73 return redirect(routes.IN_PERSON_ENROLLMENT)
76class LittlepayTokenView(TokenView):
77 """View handler for the enrollment auth token."""
79 enrollment_method = models.EnrollmentMethods.IN_PERSON
80 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
81 route_server_error = routes.IN_PERSON_SERVER_ERROR
84class EnrollmentView(SelfServiceEnrollmentIndexView):
86 route_origin = routes.IN_PERSON_ENROLLMENT
88 def get_redirect_url(self, *args, **kwargs):
89 route_name = self.agency.in_person_enrollment_index_route
90 return reverse(route_name)
93class LittlepayEnrollmentView(mixins.CommonContextMixin, LittlepayIndexView):
94 """View handler for the in-person enrollment page."""
96 enrollment_method = models.EnrollmentMethods.IN_PERSON
97 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
98 route_enrollment_retry = routes.IN_PERSON_ENROLLMENT_RETRY
99 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
100 route_server_error = routes.IN_PERSON_SERVER_ERROR
101 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
102 route_tokenize_success = littlepay_routes.IN_PERSON_ENROLLMENT_LITTLEPAY_INDEX
103 template_name = "in_person/enrollment/index_littlepay.html"
105 def _get_verified_by(self):
106 return f"{self.request.user.first_name} {self.request.user.last_name}"
109class ReenrollmentErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, SelfServiceReenrollmentErrorView):
110 """View handler for a re-enrollment attempt that is not yet within the re-enrollment window."""
112 template_name = "in_person/enrollment/reenrollment_error.html"
114 def get_context_data(self, **kwargs):
115 context = super().get_context_data(**kwargs)
117 context["flow_label"] = self.flow.label
118 return context
121class RetryView(mixins.CommonContextMixin, SelfServiceRetryView):
122 """View handler for card verification failure."""
124 template_name = "in_person/enrollment/retry.html"
125 enrollment_method = models.EnrollmentMethods.IN_PERSON
128class SystemErrorView(mixins.CommonContextMixin, SelfServiceSystemErrorView):
129 """View handler for an enrollment system error."""
131 template_name = "in_person/enrollment/system_error.html"
133 def get_origin_url(self):
134 return reverse(routes.ADMIN_INDEX)
137class ServerErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, TemplateView):
138 """View handler for errors caused by a misconfiguration or bad request."""
140 template_name = "in_person/enrollment/server_error.html"
142 def post(self, request, *args, **kwargs):
143 # the Javascript in in_person/index_littlepay.html sends a form POST to this view
144 # rather than implementing this view as a FormView, which requires instantiating the
145 # enrollment.forms.CardTokenizeFailForm, we implement post() to simply return the template via get()
146 # we thus avoid interfering with the view's lifecycle and dispatch() method
147 return super().get(request, *args, **kwargs)
150class SuccessView(mixins.CommonContextMixin, AgencySessionRequiredMixin, SelfServiceSuccessView):
151 """View handler for the final success page."""
153 template_name = "in_person/enrollment/success.html"
154 only_active_agencies = False
156 def get_context_data(self, **kwargs):
157 context = super().get_context_data(**kwargs)
159 # Check SelfServiceSuccessView's agency_short_names to see if we have a group situation
160 if context["agency_short_names"]:
161 context["success_message"] = (
162 "This rider can now use their contactless card to automatically receive a reduced fare "
163 "when they tap-to-ride at the following transit providers:"
164 )
165 else:
166 context["success_message"] = (
167 "This rider can now use their contactless card to automatically receive a reduced fare "
168 "when they tap-to-ride."
169 )
171 return context
174class SwitchioGatewayUrlView(GatewayUrlView):
175 enrollment_method = models.EnrollmentMethods.IN_PERSON
176 route_redirect = switchio_routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
177 route_server_error = routes.IN_PERSON_SERVER_ERROR
178 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
181class SwitchioEnrollmentIndexView(mixins.CommonContextMixin, SwitchioIndexView):
182 enrollment_method = models.EnrollmentMethods.IN_PERSON
183 form_class = forms.CardTokenizeSuccessForm
184 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
185 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
186 route_retry = routes.IN_PERSON_ENROLLMENT_RETRY
187 route_server_error = routes.IN_PERSON_SERVER_ERROR
188 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
189 route_tokenize_success = switchio_routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
190 template_name = "in_person/enrollment/index_switchio.html"
192 def _get_verified_by(self):
193 return f"{self.request.user.first_name} {self.request.user.last_name}"
195 def get_context_data(self, **kwargs):
196 """Add in-person specific context data."""
197 context = super().get_context_data(**kwargs)
199 if self.request.GET.get("state") == "tokenize":
200 message = "Registering this contactless card for reduced fares..."
201 else:
202 message = "Connecting with payment processor..."
204 context.update({"loading_message": message})
205 return context
207 def get(self, request, *args, **kwargs):
208 if request.GET.get("error") == "canceled": 208 ↛ 213line 208 didn't jump to line 213 because the condition on line 208 was always true
209 # the user clicked the "Back" button on the Switchio tokenization gateway
210 # send them back to the Admin index, similar to the Littlepay cancel button
211 return redirect(routes.ADMIN_INDEX)
213 return super().get(request, *args, **kwargs)