Coverage for benefits/in_person/views.py: 98%
118 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 00:01 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 00:01 +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.session import Session as LittlepaySession
20from benefits.enrollment_littlepay.views import IndexView as LittlepayIndexView, TokenView
21from benefits.enrollment_switchio.session import Session as SwitchioSession
22from benefits.enrollment_switchio.views import GatewayUrlView, IndexView as SwitchioIndexView
23from benefits.in_person import forms, mixins
24from benefits.routes import routes
26logger = logging.getLogger(__name__)
29class AdditionalAgenciesView(mixins.CommonContextMixin, SelfServiceAdditionalAgenciesView):
30 """View handler for showing the list of agencies the customer will be enrolled at (if more than one)."""
32 template_name = "in_person/additional-agencies.html"
33 only_active_agencies = False
36class EligibilityView(mixins.CommonContextMixin, FormView):
37 """CBV for the in-person eligibility flow selection form."""
39 template_name = "in_person/eligibility.html"
40 form_class = forms.InPersonEligibilityForm
42 def dispatch(self, request, *args, **kwargs):
43 """Initialize session state before handling the request."""
45 LittlepaySession(request, reset=True)
46 SwitchioSession(request, reset=True)
48 agency = session.agency(request)
49 if not agency:
50 agency = TransitAgency.for_user(request.user)
51 session.update(request, agency=agency)
52 self.agency = agency
53 return super().dispatch(request, *args, **kwargs)
55 def get_form_kwargs(self):
56 """Return the keyword arguments for instantiating the form."""
58 kwargs = super().get_form_kwargs()
59 kwargs["agency"] = self.agency
60 return kwargs
62 def form_valid(self, form):
63 """If the form is valid, set enrollment flow, eligible session, and redirect."""
65 flow_id = form.cleaned_data.get("flow")
66 flow = models.EnrollmentFlow.objects.get(id=flow_id)
67 session.update(self.request, flow=flow, eligible=True)
68 eligibility_analytics.selected_flow(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
69 eligibility_analytics.started_eligibility(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
70 eligibility_analytics.returned_success(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
71 return redirect(routes.IN_PERSON_ENROLLMENT)
74class LittlepayTokenView(TokenView):
75 """View handler for the enrollment auth token."""
77 enrollment_method = models.EnrollmentMethods.IN_PERSON
78 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
79 route_server_error = routes.IN_PERSON_SERVER_ERROR
82class EnrollmentView(SelfServiceEnrollmentIndexView):
84 route_origin = routes.IN_PERSON_ENROLLMENT
86 def get_redirect_url(self, *args, **kwargs):
87 route_name = self.agency.in_person_enrollment_index_route
88 return reverse(route_name)
91class LittlepayEnrollmentView(mixins.CommonContextMixin, LittlepayIndexView):
92 """View handler for the in-person enrollment page."""
94 enrollment_method = models.EnrollmentMethods.IN_PERSON
95 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
96 route_enrollment_retry = routes.IN_PERSON_ENROLLMENT_RETRY
97 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
98 route_server_error = routes.IN_PERSON_SERVER_ERROR
99 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
100 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_LITTLEPAY_INDEX
101 template_name = "in_person/enrollment/index_littlepay.html"
103 def _get_verified_by(self):
104 return f"{self.request.user.first_name} {self.request.user.last_name}"
107class ReenrollmentErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, SelfServiceReenrollmentErrorView):
108 """View handler for a re-enrollment attempt that is not yet within the re-enrollment window."""
110 template_name = "in_person/enrollment/reenrollment_error.html"
112 def get_context_data(self, **kwargs):
113 context = super().get_context_data(**kwargs)
115 context["flow_label"] = self.flow.label
116 return context
119class RetryView(mixins.CommonContextMixin, SelfServiceRetryView):
120 """View handler for card verification failure."""
122 template_name = "in_person/enrollment/retry.html"
123 enrollment_method = models.EnrollmentMethods.IN_PERSON
126class SystemErrorView(mixins.CommonContextMixin, SelfServiceSystemErrorView):
127 """View handler for an enrollment system error."""
129 template_name = "in_person/enrollment/system_error.html"
131 def get_origin_url(self):
132 return reverse(routes.ADMIN_INDEX)
135class ServerErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, TemplateView):
136 """View handler for errors caused by a misconfiguration or bad request."""
138 template_name = "in_person/enrollment/server_error.html"
140 def post(self, request, *args, **kwargs):
141 # the Javascript in in_person/index_littlepay.html sends a form POST to this view
142 # rather than implementing this view as a FormView, which requires instantiating the
143 # enrollment.forms.CardTokenizeFailForm, we implement post() to simply return the template via get()
144 # we thus avoid interfering with the view's lifecycle and dispatch() method
145 return super().get(request, *args, **kwargs)
148class SuccessView(mixins.CommonContextMixin, AgencySessionRequiredMixin, SelfServiceSuccessView):
149 """View handler for the final success page."""
151 template_name = "in_person/enrollment/success.html"
152 only_active_agencies = False
154 def get_context_data(self, **kwargs):
155 context = super().get_context_data(**kwargs)
157 # Check SelfServiceSuccessView's agency_short_names to see if we have a group situation
158 if context["agency_short_names"]:
159 context["success_message"] = (
160 "This rider can now use their contactless card to automatically receive a reduced fare "
161 "when they tap-to-ride at the following transit providers:"
162 )
163 else:
164 context["success_message"] = (
165 "This rider can now use their contactless card to automatically receive a reduced fare "
166 "when they tap-to-ride."
167 )
169 return context
172class SwitchioGatewayUrlView(GatewayUrlView):
173 enrollment_method = models.EnrollmentMethods.IN_PERSON
174 route_redirect = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
175 route_server_error = routes.IN_PERSON_SERVER_ERROR
176 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
179class SwitchioEnrollmentIndexView(mixins.CommonContextMixin, SwitchioIndexView):
180 enrollment_method = models.EnrollmentMethods.IN_PERSON
181 form_class = forms.CardTokenizeSuccessForm
182 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
183 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
184 route_retry = routes.IN_PERSON_ENROLLMENT_RETRY
185 route_server_error = routes.IN_PERSON_SERVER_ERROR
186 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
187 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
188 template_name = "in_person/enrollment/index_switchio.html"
190 def _get_verified_by(self):
191 return f"{self.request.user.first_name} {self.request.user.last_name}"
193 def get_context_data(self, **kwargs):
194 """Add in-person specific context data."""
195 context = super().get_context_data(**kwargs)
197 if self.request.GET.get("state") == "tokenize":
198 message = "Registering this contactless card for reduced fares..."
199 else:
200 message = "Connecting with payment processor..."
202 context.update({"loading_message": message})
203 return context
205 def get(self, request, *args, **kwargs):
206 if request.GET.get("error") == "canceled": 206 ↛ 211line 206 didn't jump to line 211 because the condition on line 206 was always true
207 # the user clicked the "Back" button on the Switchio tokenization gateway
208 # send them back to the Admin index, similar to the Littlepay cancel button
209 return redirect(routes.ADMIN_INDEX)
211 return super().get(request, *args, **kwargs)