Coverage for benefits/enrollment_switchio/views.py: 98%
107 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-10 16:52 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-10 16:52 +0000
1import logging
2from django.http import HttpRequest, JsonResponse
3from django.shortcuts import redirect
4from django.urls import reverse
5from django.views.generic import FormView, View
6import sentry_sdk
8from benefits.enrollment_switchio.models import SwitchioConfig
9from benefits.routes import routes
10from benefits.core import models
11from benefits.core.mixins import EligibleSessionRequiredMixin, AgencySessionRequiredMixin, FlowSessionRequiredMixin
12from benefits.enrollment import analytics, forms
13from benefits.enrollment.enrollment import Status, handle_enrollment_results
14from benefits.enrollment_switchio.enrollment import (
15 enroll,
16 request_registration,
17 get_registration_status,
18 get_latest_active_token_value,
19)
20from benefits.enrollment_switchio.session import Session
22logger = logging.getLogger(__name__)
25class IndexView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, EligibleSessionRequiredMixin, FormView):
26 """View for the enrollment landing page."""
28 enrollment_method = models.EnrollmentMethods.DIGITAL
29 form_class = forms.CardTokenizeSuccessForm
30 route_enrollment_success = routes.ENROLLMENT_SUCCESS
31 route_reenrollment_error = routes.ENROLLMENT_REENROLLMENT_ERROR
32 route_retry = routes.ENROLLMENT_RETRY
33 route_system_error = routes.ENROLLMENT_SYSTEM_ERROR
34 route_server_error = routes.SERVER_ERROR
35 route_tokenize_success = routes.ENROLLMENT_SWITCHIO_INDEX
36 template_name = "enrollment_switchio/index.html"
38 def get_context_data(self, **kwargs):
39 context = super().get_context_data(**kwargs)
41 request = self.request
42 flow = self.flow
44 tokenize_system_error_form = forms.CardTokenizeFailForm(
45 self.route_system_error, "form-card-tokenize-fail-system-error"
46 )
47 tokenize_success_form = forms.CardTokenizeSuccessForm(
48 action_url=self.route_tokenize_success, auto_id=True, label_suffix=""
49 )
50 context.update(
51 {
52 **flow.enrollment_index_context,
53 "forms": [tokenize_system_error_form, tokenize_success_form],
54 "form_success": tokenize_success_form.id,
55 "form_system_error": tokenize_system_error_form.id,
56 "cta_button": "tokenize_card",
57 "enrollment_method": self.enrollment_method,
58 "transit_processor": {"name": "Switchio", "website": "https://switchio.com/transport/"},
59 "locale": self._get_locale(request.LANGUAGE_CODE),
60 }
61 )
62 return context
64 def _get_locale(self, django_language_code):
65 """Given a Django language code, return the corresponding locale to use with Switchio's tokenization gateway."""
66 # mapping from Django's I18N LANGUAGE_CODE to Switchio's locales
67 locale = {"en": "en", "es": "es"}.get(django_language_code, "en")
68 return locale
70 def _get_verified_by(self):
71 return self.flow.eligibility_verifier
73 def get(self, request: HttpRequest, *args, **kwargs):
74 session = Session(request)
75 switchio_config = self.agency.switchio_config
77 if session.registration_id:
78 response = get_registration_status(switchio_config=switchio_config, registration_id=session.registration_id)
79 if response.status is Status.SUCCESS:
80 reg_state = response.registration_status.regState
81 if reg_state == "tokenization_finished":
82 # give card token to index template so it can send
83 # "finished card tokenization" event and POST either the
84 # CardTokenizeSuccessForm or CardTokenizeFailForm.
85 context_data = self.get_context_data(**kwargs)
87 context_data["card_token"] = get_latest_active_token_value(response.registration_status.tokens)
88 return self.render_to_response(context=context_data)
89 elif reg_state == "verification_failed":
90 return redirect(self.route_retry)
91 elif reg_state == "tokenization_failed": 91 ↛ 102line 91 didn't jump to line 102 because the condition on line 91 was always true
92 sentry_sdk.capture_exception(Exception("Tokenization failed"))
93 return redirect(self.route_system_error)
94 else:
95 sentry_sdk.capture_exception(response.exception)
97 if response.status is Status.SYSTEM_ERROR:
98 return redirect(self.route_system_error)
99 elif response.status is Status.EXCEPTION: 99 ↛ 102line 99 didn't jump to line 102 because the condition on line 99 was always true
100 return redirect(self.route_server_error)
102 return super().get(request=request, *args, **kwargs)
104 def form_valid(self, form):
105 switchio_config = self.agency.switchio_config
106 flow = self.flow
107 card_token = form.cleaned_data.get("card_token")
109 status, exception = enroll(request=self.request, switchio_config=switchio_config, flow=flow, token=card_token)
110 return handle_enrollment_results(
111 request=self.request,
112 status=status,
113 verified_by=self._get_verified_by(),
114 exception=exception,
115 enrollment_method=self.enrollment_method,
116 route_reenrollment_error=self.route_reenrollment_error,
117 route_success=self.route_enrollment_success,
118 route_system_error=self.route_system_error,
119 )
122class GatewayUrlView(AgencySessionRequiredMixin, EligibleSessionRequiredMixin, View):
123 """View for the tokenization gateway registration"""
125 enrollment_method = models.EnrollmentMethods.DIGITAL
126 route_redirect = routes.ENROLLMENT_SWITCHIO_INDEX
127 route_system_error = routes.ENROLLMENT_SYSTEM_ERROR
128 route_server_error = routes.SERVER_ERROR
130 def get(self, request: HttpRequest, *args, **kwargs):
131 session = Session(request)
132 switchio_config = self.agency.switchio_config
134 if session.registration_id is None or session.gateway_url is None:
135 return self._request_registration(request, switchio_config, session)
136 else:
137 response = get_registration_status(switchio_config=switchio_config, registration_id=session.registration_id)
139 if response.status is Status.SUCCESS:
140 # if the registration session is no longer valid, request a new registration session.
141 if response.registration_status.regState in ["expired", "deleted"]:
142 return self._request_registration(request, switchio_config, session)
143 else:
144 return self._gateway_url_response(session)
145 else:
146 logger.debug(f"Error occurred while attempting to get registration status for {session.registration_id}")
147 sentry_sdk.capture_exception(response.exception)
148 analytics.failed_pretokenization_request(request, response.status_code, self.enrollment_method)
150 if response.status is Status.SYSTEM_ERROR:
151 redirect = reverse(self.route_system_error)
152 else:
153 redirect = reverse(self.route_server_error)
155 data = {"redirect": redirect}
156 return JsonResponse(data)
158 def _request_registration(self, request: HttpRequest, switchio_config: SwitchioConfig, session: Session) -> JsonResponse:
159 response = request_registration(request, switchio_config, self.route_redirect)
161 if response.status is Status.SUCCESS:
162 registration = response.registration
163 session.registration_id = registration.regId
164 session.gateway_url = registration.gtwUrl
166 return self._gateway_url_response(session)
167 else:
168 logger.debug("Error occurred while requesting a tokenization gateway registration", exc_info=response.exception)
169 sentry_sdk.capture_exception(response.exception)
170 analytics.failed_pretokenization_request(request, response.status_code, self.enrollment_method)
172 if response.status is Status.SYSTEM_ERROR:
173 redirect = reverse(self.route_system_error)
174 else:
175 redirect = reverse(self.route_server_error)
177 data = {"redirect": redirect}
178 return JsonResponse(data)
180 def _gateway_url_response(self, session: Session):
181 data = {"gateway_url": session.gateway_url}
182 return JsonResponse(data)