Coverage for benefits / enrollment_switchio / views.py: 99%
108 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-01 15:39 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-01 15:39 +0000
1import logging
3import sentry_sdk
4from django.http import HttpRequest, JsonResponse
5from django.shortcuts import redirect
6from django.urls import reverse
7from django.views.generic import FormView, View
9from benefits.core import models
10from benefits.core.mixins import AgencySessionRequiredMixin, EligibleSessionRequiredMixin
11from benefits.enrollment import analytics, forms
12from benefits.enrollment.enrollment import Status, handle_enrollment_results
13from benefits.enrollment.views import IndexContextMixin
14from benefits.enrollment_switchio.enrollment import (
15 enroll,
16 get_latest_active_token_value,
17 get_registration_status,
18 request_registration,
19)
20from benefits.enrollment_switchio.models import SwitchioConfig
21from benefits.enrollment_switchio.session import Session
22from benefits.routes import routes
24logger = logging.getLogger(__name__)
27class IndexView(AgencySessionRequiredMixin, EligibleSessionRequiredMixin, IndexContextMixin, FormView):
28 """View for the enrollment landing page."""
30 enrollment_method = models.EnrollmentMethods.DIGITAL
31 form_class = forms.CardTokenizeSuccessForm
32 route_enrollment_success = routes.ENROLLMENT_SUCCESS
33 route_reenrollment_error = routes.ENROLLMENT_REENROLLMENT_ERROR
34 route_retry = routes.ENROLLMENT_RETRY
35 route_system_error = routes.ENROLLMENT_SYSTEM_ERROR
36 route_server_error = routes.SERVER_ERROR
37 route_tokenize_success = routes.ENROLLMENT_SWITCHIO_INDEX
38 template_name = "enrollment_switchio/index.html"
40 def get_context_data(self, **kwargs):
41 context = super().get_context_data(**kwargs)
43 request = self.request
45 tokenize_system_error_form = forms.CardTokenizeFailForm(
46 self.route_system_error, "form-card-tokenize-fail-system-error"
47 )
48 tokenize_success_form = forms.CardTokenizeSuccessForm(
49 action_url=self.route_tokenize_success, auto_id=True, label_suffix=""
50 )
51 context.update(
52 {
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 group = self.group
108 card_token = form.cleaned_data.get("card_token")
110 status, exception = enroll(
111 request=self.request, switchio_config=switchio_config, flow=flow, group=group, token=card_token
112 )
113 return handle_enrollment_results(
114 request=self.request,
115 status=status,
116 verified_by=self._get_verified_by(),
117 exception=exception,
118 enrollment_method=self.enrollment_method,
119 route_reenrollment_error=self.route_reenrollment_error,
120 route_success=self.route_enrollment_success,
121 route_system_error=self.route_system_error,
122 )
125class GatewayUrlView(AgencySessionRequiredMixin, EligibleSessionRequiredMixin, View):
126 """View for the tokenization gateway registration"""
128 enrollment_method = models.EnrollmentMethods.DIGITAL
129 route_redirect = routes.ENROLLMENT_SWITCHIO_INDEX
130 route_system_error = routes.ENROLLMENT_SYSTEM_ERROR
131 route_server_error = routes.SERVER_ERROR
133 def get(self, request: HttpRequest, *args, **kwargs):
134 session = Session(request)
135 switchio_config = self.agency.switchio_config
137 if session.registration_id is None or session.gateway_url is None:
138 return self._request_registration(request, switchio_config, session)
139 else:
140 response = get_registration_status(switchio_config=switchio_config, registration_id=session.registration_id)
142 if response.status is Status.SUCCESS:
143 # if the registration session is no longer valid, request a new registration session.
144 if response.registration_status.regState in ["expired", "deleted"]:
145 return self._request_registration(request, switchio_config, session)
146 else:
147 return self._gateway_url_response(session)
148 else:
149 logger.debug(f"Error occurred while attempting to get registration status for {session.registration_id}")
150 sentry_sdk.capture_exception(response.exception)
151 analytics.failed_pretokenization_request(request, "switchio", response.status_code, self.enrollment_method)
153 if response.status is Status.SYSTEM_ERROR:
154 redirect = reverse(self.route_system_error)
155 else:
156 redirect = reverse(self.route_server_error)
158 data = {"redirect": redirect}
159 return JsonResponse(data)
161 def _request_registration(self, request: HttpRequest, switchio_config: SwitchioConfig, session: Session) -> JsonResponse:
162 response = request_registration(request, switchio_config, self.route_redirect)
164 if response.status is Status.SUCCESS:
165 registration = response.registration
166 session.registration_id = registration.regId
167 session.gateway_url = registration.gtwUrl
169 return self._gateway_url_response(session)
170 else:
171 logger.debug("Error occurred while requesting a tokenization gateway registration", exc_info=response.exception)
172 sentry_sdk.capture_exception(response.exception)
173 analytics.failed_pretokenization_request(request, "switchio", response.status_code, self.enrollment_method)
175 if response.status is Status.SYSTEM_ERROR:
176 redirect = reverse(self.route_system_error)
177 else:
178 redirect = reverse(self.route_server_error)
180 data = {"redirect": redirect}
181 return JsonResponse(data)
183 def _gateway_url_response(self, session: Session):
184 data = {"gateway_url": session.gateway_url}
185 return JsonResponse(data)