Coverage for benefits / in_person / views.py: 98%
107 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +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.eligibility import analytics as eligibility_analytics
11from benefits.enrollment.views import (
12 IndexView,
13 ReenrollmentErrorView as DigitalReenrollmentErrorView,
14 RetryView as DigitalRetryView,
15 SuccessView as DigitalSuccessView,
16 SystemErrorView as DigitalSystemErrorView,
17)
18from benefits.enrollment_littlepay.session import Session as LittlepaySession
19from benefits.enrollment_littlepay.views import IndexView as LittlepayIndexView, TokenView
20from benefits.enrollment_switchio.session import Session as SwitchioSession
21from benefits.enrollment_switchio.views import GatewayUrlView, IndexView as SwitchioIndexView
22from benefits.in_person import forms, mixins
23from benefits.routes import routes
25logger = logging.getLogger(__name__)
28class EligibilityView(mixins.CommonContextMixin, FormView):
29 """CBV for the in-person eligibility flow selection form."""
31 template_name = "in_person/eligibility.html"
32 form_class = forms.InPersonEligibilityForm
34 def dispatch(self, request, *args, **kwargs):
35 """Initialize session state before handling the request."""
37 LittlepaySession(request, reset=True)
38 SwitchioSession(request, reset=True)
40 agency = session.agency(request)
41 if not agency:
42 agency = TransitAgency.for_user(request.user)
43 session.update(request, agency=agency)
44 self.agency = agency
45 return super().dispatch(request, *args, **kwargs)
47 def get_form_kwargs(self):
48 """Return the keyword arguments for instantiating the form."""
50 kwargs = super().get_form_kwargs()
51 kwargs["agency"] = self.agency
52 return kwargs
54 def form_valid(self, form):
55 """If the form is valid, set enrollment flow, eligible session, and redirect."""
57 flow_id = form.cleaned_data.get("flow")
58 flow = models.EnrollmentFlow.objects.get(id=flow_id)
59 session.update(self.request, flow=flow, eligible=True)
60 eligibility_analytics.selected_flow(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
61 eligibility_analytics.started_eligibility(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
62 eligibility_analytics.returned_success(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
63 return redirect(routes.IN_PERSON_ENROLLMENT)
66class LittlepayTokenView(TokenView):
67 """View handler for the enrollment auth token."""
69 enrollment_method = models.EnrollmentMethods.IN_PERSON
70 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
71 route_server_error = routes.IN_PERSON_SERVER_ERROR
74class EnrollmentView(IndexView):
76 route_origin = routes.IN_PERSON_ENROLLMENT
78 def get_redirect_url(self, *args, **kwargs):
79 route_name = self.agency.in_person_enrollment_index_route
80 return reverse(route_name)
83class LittlepayEnrollmentView(mixins.CommonContextMixin, LittlepayIndexView):
84 """View handler for the in-person enrollment page."""
86 enrollment_method = models.EnrollmentMethods.IN_PERSON
87 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
88 route_enrollment_retry = routes.IN_PERSON_ENROLLMENT_RETRY
89 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
90 route_server_error = routes.IN_PERSON_SERVER_ERROR
91 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
92 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_LITTLEPAY_INDEX
93 template_name = "in_person/enrollment/index_littlepay.html"
95 def _get_verified_by(self):
96 return f"{self.request.user.first_name} {self.request.user.last_name}"
99class ReenrollmentErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, DigitalReenrollmentErrorView):
100 """View handler for a re-enrollment attempt that is not yet within the re-enrollment window."""
102 template_name = "in_person/enrollment/reenrollment_error.html"
104 def get_context_data(self, **kwargs):
105 context = super().get_context_data(**kwargs)
107 context["flow_label"] = self.flow.label
108 return context
111class RetryView(mixins.CommonContextMixin, DigitalRetryView):
112 """View handler for card verification failure."""
114 template_name = "in_person/enrollment/retry.html"
115 enrollment_method = models.EnrollmentMethods.IN_PERSON
118class SystemErrorView(mixins.CommonContextMixin, DigitalSystemErrorView):
119 """View handler for an enrollment system error."""
121 template_name = "in_person/enrollment/system_error.html"
123 def get_origin_url(self):
124 return reverse(routes.ADMIN_INDEX)
127class ServerErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, TemplateView):
128 """View handler for errors caused by a misconfiguration or bad request."""
130 template_name = "in_person/enrollment/server_error.html"
132 def post(self, request, *args, **kwargs):
133 # the Javascript in in_person/index_littlepay.html sends a form POST to this view
134 # rather than implementing this view as a FormView, which requires instantiating the
135 # enrollment.forms.CardTokenizeFailForm, we implement post() to simply return the template via get()
136 # we thus avoid interfering with the view's lifecycle and dispatch() method
137 return super().get(request, *args, **kwargs)
140class SuccessView(mixins.CommonContextMixin, AgencySessionRequiredMixin, DigitalSuccessView):
141 """View handler for the final success page."""
143 template_name = "in_person/enrollment/success.html"
146class SwitchioGatewayUrlView(GatewayUrlView):
147 enrollment_method = models.EnrollmentMethods.IN_PERSON
148 route_redirect = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
149 route_server_error = routes.IN_PERSON_SERVER_ERROR
150 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
153class SwitchioEnrollmentIndexView(mixins.CommonContextMixin, SwitchioIndexView):
154 enrollment_method = models.EnrollmentMethods.IN_PERSON
155 form_class = forms.CardTokenizeSuccessForm
156 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
157 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
158 route_retry = routes.IN_PERSON_ENROLLMENT_RETRY
159 route_server_error = routes.IN_PERSON_SERVER_ERROR
160 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
161 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
162 template_name = "in_person/enrollment/index_switchio.html"
164 def _get_verified_by(self):
165 return f"{self.request.user.first_name} {self.request.user.last_name}"
167 def get_context_data(self, **kwargs):
168 """Add in-person specific context data."""
169 context = super().get_context_data(**kwargs)
171 if self.request.GET.get("state") == "tokenize":
172 message = "Registering this contactless card for reduced fares..."
173 else:
174 message = "Connecting with payment processor..."
176 context.update({"loading_message": message})
177 return context
179 def get(self, request, *args, **kwargs):
180 if request.GET.get("error") == "canceled": 180 ↛ 185line 180 didn't jump to line 185 because the condition on line 180 was always true
181 # the user clicked the "Back" button on the Switchio tokenization gateway
182 # send them back to the Admin index, similar to the Littlepay cancel button
183 return redirect(routes.ADMIN_INDEX)
185 return super().get(request, *args, **kwargs)