Coverage for benefits / in_person / views.py: 98%
107 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-22 19:08 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-22 19:08 +0000
1import logging
3from django.shortcuts import redirect
4from django.urls import reverse
5from django.views.generic import FormView, TemplateView
7from benefits.core.models.transit import TransitAgency
8from benefits.core import models, session
9from benefits.core.mixins import AgencySessionRequiredMixin
10from benefits.eligibility import analytics as eligibility_analytics
11from benefits.enrollment.views import (
12 IndexView,
13 ReenrollmentErrorView as DigitalReenrollmentErrorView,
14 RetryView as DigitalRetryView,
15 SystemErrorView as DigitalSystemErrorView,
16 SuccessView as DigitalSuccessView,
17)
18from benefits.enrollment_littlepay.session import Session as LittlepaySession
19from benefits.enrollment_littlepay.views import TokenView, IndexView as LittlepayIndexView
20from benefits.enrollment_switchio.session import Session as SwitchioSession
21from 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 EligibilityView(mixins.CommonContextMixin, FormView):
30 """CBV for the in-person eligibility flow selection form."""
32 template_name = "in_person/eligibility.html"
33 form_class = forms.InPersonEligibilityForm
35 def dispatch(self, request, *args, **kwargs):
36 """Initialize session state before handling the request."""
38 LittlepaySession(request, reset=True)
39 SwitchioSession(request, reset=True)
41 agency = session.agency(request)
42 if not agency:
43 agency = TransitAgency.for_user(request.user)
44 session.update(request, agency=agency)
45 self.agency = agency
46 return super().dispatch(request, *args, **kwargs)
48 def get_form_kwargs(self):
49 """Return the keyword arguments for instantiating the form."""
51 kwargs = super().get_form_kwargs()
52 kwargs["agency"] = self.agency
53 return kwargs
55 def form_valid(self, form):
56 """If the form is valid, set enrollment flow, eligible session, and redirect."""
58 flow_id = form.cleaned_data.get("flow")
59 flow = models.EnrollmentFlow.objects.get(id=flow_id)
60 session.update(self.request, flow=flow, eligible=True)
61 eligibility_analytics.selected_flow(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
62 eligibility_analytics.started_eligibility(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
63 eligibility_analytics.returned_success(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
64 return redirect(routes.IN_PERSON_ENROLLMENT)
67class LittlepayTokenView(TokenView):
68 """View handler for the enrollment auth token."""
70 enrollment_method = models.EnrollmentMethods.IN_PERSON
71 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
72 route_server_error = routes.IN_PERSON_SERVER_ERROR
75class EnrollmentView(IndexView):
77 route_origin = routes.IN_PERSON_ENROLLMENT
79 def get_redirect_url(self, *args, **kwargs):
80 route_name = self.agency.in_person_enrollment_index_route
81 return reverse(route_name)
84class LittlepayEnrollmentView(mixins.CommonContextMixin, LittlepayIndexView):
85 """View handler for the in-person enrollment page."""
87 enrollment_method = models.EnrollmentMethods.IN_PERSON
88 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
89 route_enrollment_retry = routes.IN_PERSON_ENROLLMENT_RETRY
90 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
91 route_server_error = routes.IN_PERSON_SERVER_ERROR
92 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
93 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_LITTLEPAY_INDEX
94 template_name = "in_person/enrollment/index_littlepay.html"
96 def _get_verified_by(self):
97 return f"{self.request.user.first_name} {self.request.user.last_name}"
100class ReenrollmentErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, DigitalReenrollmentErrorView):
101 """View handler for a re-enrollment attempt that is not yet within the re-enrollment window."""
103 template_name = "in_person/enrollment/reenrollment_error.html"
105 def get_context_data(self, **kwargs):
106 context = super().get_context_data(**kwargs)
108 context["flow_label"] = self.flow.label
109 return context
112class RetryView(mixins.CommonContextMixin, DigitalRetryView):
113 """View handler for card verification failure."""
115 template_name = "in_person/enrollment/retry.html"
116 enrollment_method = models.EnrollmentMethods.IN_PERSON
119class SystemErrorView(mixins.CommonContextMixin, DigitalSystemErrorView):
120 """View handler for an enrollment system error."""
122 template_name = "in_person/enrollment/system_error.html"
124 def get_origin_url(self):
125 return reverse(routes.ADMIN_INDEX)
128class ServerErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, TemplateView):
129 """View handler for errors caused by a misconfiguration or bad request."""
131 template_name = "in_person/enrollment/server_error.html"
133 def post(self, request, *args, **kwargs):
134 # the Javascript in in_person/index_littlepay.html sends a form POST to this view
135 # rather than implementing this view as a FormView, which requires instantiating the
136 # enrollment.forms.CardTokenizeFailForm, we implement post() to simply return the template via get()
137 # we thus avoid interfering with the view's lifecycle and dispatch() method
138 return super().get(request, *args, **kwargs)
141class SuccessView(mixins.CommonContextMixin, AgencySessionRequiredMixin, DigitalSuccessView):
142 """View handler for the final success page."""
144 template_name = "in_person/enrollment/success.html"
147class SwitchioGatewayUrlView(GatewayUrlView):
148 enrollment_method = models.EnrollmentMethods.IN_PERSON
149 route_redirect = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
150 route_server_error = routes.IN_PERSON_SERVER_ERROR
151 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
154class SwitchioEnrollmentIndexView(mixins.CommonContextMixin, SwitchioIndexView):
155 enrollment_method = models.EnrollmentMethods.IN_PERSON
156 form_class = forms.CardTokenizeSuccessForm
157 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS
158 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR
159 route_retry = routes.IN_PERSON_ENROLLMENT_RETRY
160 route_server_error = routes.IN_PERSON_SERVER_ERROR
161 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR
162 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX
163 template_name = "in_person/enrollment/index_switchio.html"
165 def _get_verified_by(self):
166 return f"{self.request.user.first_name} {self.request.user.last_name}"
168 def get_context_data(self, **kwargs):
169 """Add in-person specific context data."""
170 context = super().get_context_data(**kwargs)
172 if self.request.GET.get("state") == "tokenize":
173 message = "Registering this contactless card for reduced fares..."
174 else:
175 message = "Connecting with payment processor..."
177 context.update({"loading_message": message})
178 return context
180 def get(self, request, *args, **kwargs):
181 if request.GET.get("error") == "canceled": 181 ↛ 186line 181 didn't jump to line 186 because the condition on line 181 was always true
182 # the user clicked the "Back" button on the Switchio tokenization gateway
183 # send them back to the Admin index, similar to the Littlepay cancel button
184 return redirect(routes.ADMIN_INDEX)
186 return super().get(request, *args, **kwargs)