Coverage for benefits/in_person/views.py: 93%
122 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-06 20:07 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-06 20:07 +0000
1import logging
3from django.contrib.admin import site as admin_site
4from django.http import JsonResponse
5from django.template.response import TemplateResponse
6from django.shortcuts import redirect
7from django.urls import reverse
8import sentry_sdk
11from benefits.routes import routes
12from benefits.core import models, session
13from benefits.eligibility import analytics as eligibility_analytics
14from benefits.enrollment import analytics as enrollment_analytics
15from benefits.enrollment.enrollment import Status
16from benefits.enrollment_littlepay.enrollment import request_card_tokenization_access, enroll
17from benefits.enrollment_littlepay.session import Session as LittlepaySession
19from benefits.in_person import forms
21logger = logging.getLogger(__name__)
24def eligibility(request):
25 """View handler for the in-person eligibility flow selection form."""
27 agency = session.agency(request)
28 context = {
29 **admin_site.each_context(request),
30 "form": forms.InPersonEligibilityForm(agency=agency),
31 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
32 }
34 if request.method == "POST":
35 form = forms.InPersonEligibilityForm(data=request.POST, agency=agency)
37 if form.is_valid():
38 flow_id = form.cleaned_data.get("flow")
39 flow = models.EnrollmentFlow.objects.get(id=flow_id)
40 session.update(request, flow=flow)
41 eligibility_analytics.selected_flow(request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
42 eligibility_analytics.started_eligibility(request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
44 in_person_enrollment = reverse(routes.IN_PERSON_ENROLLMENT)
45 response = redirect(in_person_enrollment)
46 else:
47 context["form"] = form
48 response = TemplateResponse(request, "in_person/eligibility.html", context)
49 else:
50 response = TemplateResponse(request, "in_person/eligibility.html", context)
52 return response
55def token(request):
56 """View handler for the enrollment auth token."""
57 session = LittlepaySession(request)
59 if not session.access_token_valid():
60 response = request_card_tokenization_access(request)
62 if response.status is Status.SUCCESS:
63 session.access_token = response.access_token
64 session.access_token_expiry = response.expires_at
65 elif response.status is Status.SYSTEM_ERROR or response.status is Status.EXCEPTION: 65 ↛ 80line 65 didn't jump to line 80 because the condition on line 65 was always true
66 logger.debug("Error occurred while requesting access token", exc_info=response.exception)
67 sentry_sdk.capture_exception(response.exception)
68 enrollment_analytics.failed_access_token_request(
69 request, response.status_code, enrollment_method=models.EnrollmentMethods.IN_PERSON
70 )
72 if response.status is Status.SYSTEM_ERROR:
73 redirect = reverse(routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR)
74 else:
75 redirect = reverse(routes.IN_PERSON_SERVER_ERROR)
77 data = {"redirect": redirect}
78 return JsonResponse(data)
80 data = {"token": session.access_token}
82 return JsonResponse(data)
85def enrollment(request):
86 """View handler for the in-person enrollment page."""
87 # POST back after transit processor form, process card token
88 if request.method == "POST":
89 form = forms.CardTokenizeSuccessForm(request.POST)
90 if not form.is_valid():
91 raise Exception("Invalid card token form")
93 flow = session.flow(request)
94 eligibility_analytics.returned_success(request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON)
95 card_token = form.cleaned_data.get("card_token")
96 status, exception = enroll(request, card_token)
98 match (status):
99 case Status.SUCCESS:
100 agency = session.agency(request)
101 expiry = session.enrollment_expiry(request)
102 verified_by = f"{request.user.first_name} {request.user.last_name}"
103 event = models.EnrollmentEvent.objects.create(
104 transit_agency=agency,
105 enrollment_flow=flow,
106 enrollment_method=models.EnrollmentMethods.IN_PERSON,
107 verified_by=verified_by,
108 expiration_datetime=expiry,
109 )
110 event.save()
111 enrollment_analytics.returned_success(
112 request, flow.group_id, enrollment_method=models.EnrollmentMethods.IN_PERSON
113 )
114 return redirect(routes.IN_PERSON_ENROLLMENT_SUCCESS)
116 case Status.SYSTEM_ERROR:
117 enrollment_analytics.returned_error(
118 request, str(exception), enrollment_method=models.EnrollmentMethods.IN_PERSON
119 )
120 sentry_sdk.capture_exception(exception)
121 return redirect(routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR)
123 case Status.EXCEPTION:
124 enrollment_analytics.returned_error(
125 request, str(exception), enrollment_method=models.EnrollmentMethods.IN_PERSON
126 )
127 sentry_sdk.capture_exception(exception)
128 return redirect(routes.IN_PERSON_SERVER_ERROR)
130 case Status.REENROLLMENT_ERROR: 130 ↛ exitline 130 didn't return from function 'enrollment' because the pattern on line 130 always matched
131 enrollment_analytics.returned_error(
132 request, "Re-enrollment error.", enrollment_method=models.EnrollmentMethods.IN_PERSON
133 )
134 return redirect(routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR)
135 # GET enrollment index
136 else:
137 agency = session.agency(request)
139 tokenize_retry_form = forms.CardTokenizeFailForm(routes.IN_PERSON_ENROLLMENT_RETRY, "form-card-tokenize-fail-retry")
140 tokenize_server_error_form = forms.CardTokenizeFailForm(
141 routes.IN_PERSON_SERVER_ERROR, "form-card-tokenize-fail-server-error"
142 )
143 tokenize_system_error_form = forms.CardTokenizeFailForm(
144 routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR, "form-card-tokenize-fail-system-error"
145 )
146 tokenize_success_form = forms.CardTokenizeSuccessForm(
147 action_url=routes.IN_PERSON_ENROLLMENT, auto_id=True, label_suffix=""
148 )
150 context = {
151 **admin_site.each_context(request),
152 "forms": [tokenize_retry_form, tokenize_server_error_form, tokenize_system_error_form, tokenize_success_form],
153 "cta_button": "tokenize_card",
154 "enrollment_method": models.EnrollmentMethods.IN_PERSON,
155 "token_field": "card_token",
156 "form_retry": tokenize_retry_form.id,
157 "form_server_error": tokenize_server_error_form.id,
158 "form_success": tokenize_success_form.id,
159 "form_system_error": tokenize_system_error_form.id,
160 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
161 }
163 match agency.littlepay_config.environment:
164 case models.Environment.QA.value: 164 ↛ 167line 164 didn't jump to line 167 because the pattern on line 164 always matched
165 url = "https://verify.qa.littlepay.com/assets/js/littlepay.min.js"
166 card_tokenize_env = "https://verify.qa.littlepay.com"
167 case models.Environment.PROD.value:
168 url = "https://verify.littlepay.com/assets/js/littlepay.min.js"
169 card_tokenize_env = "https://verify.littlepay.com"
170 case _:
171 raise ValueError("Unrecognized environment value")
173 transit_processor_context = dict(
174 name="Littlepay", website="https://littlepay.com", card_tokenize_url=url, card_tokenize_env=card_tokenize_env
175 )
176 context.update({"transit_processor": transit_processor_context})
178 return TemplateResponse(request, "in_person/enrollment/index.html", context)
181def reenrollment_error(request):
182 """View handler for a re-enrollment attempt that is not yet within the re-enrollment window."""
184 agency = session.agency(request)
185 context = {
186 **admin_site.each_context(request),
187 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
188 }
190 flow = session.flow(request)
191 context["flow_label"] = flow.label
193 return TemplateResponse(request, "in_person/enrollment/reenrollment_error.html", context)
196def retry(request):
197 """View handler for card verification failure."""
198 # enforce POST-only route for sending analytics
199 if request.method == "POST":
200 enrollment_analytics.returned_retry(request, enrollment_method=models.EnrollmentMethods.IN_PERSON)
202 agency = session.agency(request)
203 context = {
204 **admin_site.each_context(request),
205 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
206 }
208 return TemplateResponse(request, "in_person/enrollment/retry.html", context)
211def system_error(request):
212 """View handler for an enrollment system error."""
213 agency = session.agency(request)
214 context = {
215 **admin_site.each_context(request),
216 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
217 }
219 return TemplateResponse(request, "in_person/enrollment/system_error.html", context)
222def server_error(request):
223 """View handler for errors caused by a misconfiguration or bad request."""
224 agency = session.agency(request)
225 context = {
226 **admin_site.each_context(request),
227 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
228 }
230 return TemplateResponse(request, "in_person/enrollment/server_error.html", context)
233def success(request):
234 """View handler for the final success page."""
235 agency = session.agency(request)
236 context = {
237 **admin_site.each_context(request),
238 "title": f"{agency.long_name} | In-person enrollment | {admin_site.site_title}",
239 }
241 return TemplateResponse(request, "in_person/enrollment/success.html", context)