Coverage for benefits/enrollment/views.py: 95%
102 statements
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-22 18:00 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-22 18:00 +0000
1"""
2The enrollment application: view definitions for the benefits enrollment flow.
3"""
5import logging
8from django.http import JsonResponse
9from django.template.response import TemplateResponse
10from django.urls import reverse
11from django.utils.decorators import decorator_from_middleware
12import sentry_sdk
14from benefits.routes import routes
15from benefits.core import session
16from benefits.core.middleware import EligibleSessionRequired, FlowSessionRequired, pageview_decorator
18from benefits.core import models
19from . import analytics, forms
20from .enrollment import Status, request_card_tokenization_access, enroll
22TEMPLATE_RETRY = "enrollment/retry.html"
23TEMPLATE_SYSTEM_ERROR = "enrollment/system_error.html"
26logger = logging.getLogger(__name__)
29@decorator_from_middleware(EligibleSessionRequired)
30def token(request):
31 """View handler for the enrollment auth token."""
32 if not session.enrollment_token_valid(request):
33 response = request_card_tokenization_access(request)
35 if response.status is Status.SUCCESS:
36 session.update(request, enrollment_token=response.access_token, enrollment_token_exp=response.expires_at)
37 elif response.status is Status.SYSTEM_ERROR or response.status is Status.EXCEPTION: 37 ↛ 50line 37 didn't jump to line 50 because the condition on line 37 was always true
38 logger.debug("Error occurred while requesting access token", exc_info=response.exception)
39 sentry_sdk.capture_exception(response.exception)
40 analytics.failed_access_token_request(request, response.status_code)
42 if response.status is Status.SYSTEM_ERROR:
43 redirect = reverse(routes.ENROLLMENT_SYSTEM_ERROR)
44 else:
45 redirect = reverse(routes.SERVER_ERROR)
47 data = {"redirect": redirect}
48 return JsonResponse(data)
50 data = {"token": session.enrollment_token(request)}
52 return JsonResponse(data)
55@decorator_from_middleware(EligibleSessionRequired)
56def index(request):
57 """View handler for the enrollment landing page."""
58 session.update(request, origin=reverse(routes.ENROLLMENT_INDEX))
60 # POST back after transit processor form, process card token
61 if request.method == "POST":
62 form = forms.CardTokenizeSuccessForm(request.POST)
63 if not form.is_valid():
64 raise Exception("Invalid card token form")
66 card_token = form.cleaned_data.get("card_token")
67 status, exception = enroll(request, card_token)
69 match (status):
70 case Status.SUCCESS:
71 agency = session.agency(request)
72 flow = session.flow(request)
73 expiry = session.enrollment_expiry(request)
74 oauth_extra_claims = session.oauth_extra_claims(request)
75 # EnrollmentEvent expects a string value for extra_claims
76 if oauth_extra_claims: 76 ↛ 79line 76 didn't jump to line 79 because the condition on line 76 was always true
77 str_extra_claims = ", ".join(oauth_extra_claims)
78 else:
79 str_extra_claims = ""
80 event = models.EnrollmentEvent.objects.create(
81 transit_agency=agency,
82 enrollment_flow=flow,
83 enrollment_method=models.EnrollmentMethods.DIGITAL,
84 verified_by=flow.eligibility_verifier,
85 expiration_datetime=expiry,
86 extra_claims=str_extra_claims,
87 )
88 event.save()
89 analytics.returned_success(request, flow.group_id, extra_claims=oauth_extra_claims)
90 return success(request)
92 case Status.SYSTEM_ERROR:
93 analytics.returned_error(request, str(exception))
94 sentry_sdk.capture_exception(exception)
95 return system_error(request)
97 case Status.EXCEPTION:
98 analytics.returned_error(request, str(exception))
99 raise exception
101 case Status.REENROLLMENT_ERROR: 101 ↛ exitline 101 didn't return from function 'index' because the pattern on line 101 always matched
102 return reenrollment_error(request)
104 # GET enrollment index
105 else:
106 agency = session.agency(request)
107 flow = session.flow(request)
109 tokenize_retry_form = forms.CardTokenizeFailForm(routes.ENROLLMENT_RETRY, "form-card-tokenize-fail-retry")
110 tokenize_server_error_form = forms.CardTokenizeFailForm(routes.SERVER_ERROR, "form-card-tokenize-fail-server-error")
111 tokenize_system_error_form = forms.CardTokenizeFailForm(
112 routes.ENROLLMENT_SYSTEM_ERROR, "form-card-tokenize-fail-system-error"
113 )
114 tokenize_success_form = forms.CardTokenizeSuccessForm(
115 action_url=routes.ENROLLMENT_INDEX, auto_id=True, label_suffix=""
116 )
118 # mapping from Django's I18N LANGUAGE_CODE to Littlepay's overlay language code
119 overlay_language = {"en": "en", "es": "es-419"}.get(request.LANGUAGE_CODE, "en")
121 context = {
122 "forms": [tokenize_retry_form, tokenize_server_error_form, tokenize_system_error_form, tokenize_success_form],
123 "cta_button": "tokenize_card",
124 "card_tokenize_env": agency.transit_processor.card_tokenize_env,
125 "card_tokenize_func": agency.transit_processor.card_tokenize_func,
126 "card_tokenize_url": agency.transit_processor.card_tokenize_url,
127 "enrollment_method": models.EnrollmentMethods.DIGITAL,
128 "token_field": "card_token",
129 "form_retry": tokenize_retry_form.id,
130 "form_server_error": tokenize_server_error_form.id,
131 "form_success": tokenize_success_form.id,
132 "form_system_error": tokenize_system_error_form.id,
133 "overlay_language": overlay_language,
134 }
136 logger.debug(f'card_tokenize_url: {context["card_tokenize_url"]}')
138 return TemplateResponse(request, flow.enrollment_index_template, context)
141@decorator_from_middleware(EligibleSessionRequired)
142def reenrollment_error(request):
143 """View handler for a re-enrollment attempt that is not yet within the re-enrollment window."""
144 flow = session.flow(request)
146 if flow.reenrollment_error_template is None:
147 raise Exception(f"Re-enrollment error with null template on: {flow}")
149 if session.logged_in(request) and flow.claims_provider.supports_sign_out: 149 ↛ 152line 149 didn't jump to line 152 because the condition on line 149 was never true
150 # overwrite origin for a logged in user
151 # if they click the logout button, they are taken to the new route
152 session.update(request, origin=reverse(routes.LOGGED_OUT))
154 analytics.returned_error(request, "Re-enrollment error.")
156 return TemplateResponse(request, flow.reenrollment_error_template)
159@decorator_from_middleware(EligibleSessionRequired)
160def retry(request):
161 """View handler for a recoverable failure condition."""
162 analytics.returned_retry(request)
163 return TemplateResponse(request, TEMPLATE_RETRY)
166@decorator_from_middleware(EligibleSessionRequired)
167def system_error(request):
168 """View handler for an enrollment system error."""
170 # overwrite origin so that CTA takes user to agency index
171 agency = session.agency(request)
172 session.update(request, origin=agency.index_url)
174 return TemplateResponse(request, TEMPLATE_SYSTEM_ERROR)
177@pageview_decorator
178@decorator_from_middleware(EligibleSessionRequired)
179@decorator_from_middleware(FlowSessionRequired)
180def success(request):
181 """View handler for the final success page."""
182 request.path = "/enrollment/success"
183 session.update(request, origin=reverse(routes.ENROLLMENT_SUCCESS))
185 flow = session.flow(request)
187 if session.logged_in(request) and flow.claims_provider.supports_sign_out:
188 # overwrite origin for a logged in user
189 # if they click the logout button, they are taken to the new route
190 session.update(request, origin=reverse(routes.LOGGED_OUT))
192 context = {"redirect_to": request.path}
193 return TemplateResponse(request, flow.enrollment_success_template, context)