Coverage for benefits / enrollment_switchio / views.py: 98%
107 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +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 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, "switchio", 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, "switchio", 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)