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