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

1import logging 

2 

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 

8 

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 

22 

23from .routes import routes 

24 

25logger = logging.getLogger(__name__) 

26 

27 

28class IndexView(AgencySessionRequiredMixin, EligibleSessionRequiredMixin, IndexContextMixin, FormView): 

29 """View for the enrollment landing page.""" 

30 

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" 

40 

41 def get_context_data(self, **kwargs): 

42 context = super().get_context_data(**kwargs) 

43 

44 request = self.request 

45 

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 

65 

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 

71 

72 def _get_verified_by(self): 

73 return self.flow.eligibility_verifier 

74 

75 def get(self, request: HttpRequest, *args, **kwargs): 

76 session = Session(request) 

77 switchio_config = self.agency.transit_processor 

78 

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) 

88 

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) 

98 

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) 

103 

104 return super().get(request=request, *args, **kwargs) 

105 

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") 

111 

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 ) 

125 

126 

127class GatewayUrlView(AgencySessionRequiredMixin, EligibleSessionRequiredMixin, View): 

128 """View for the tokenization gateway registration""" 

129 

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 

134 

135 def get(self, request: HttpRequest, *args, **kwargs): 

136 session = Session(request) 

137 switchio_config = self.agency.transit_processor 

138 

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) 

143 

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) 

154 

155 if response.status is Status.SYSTEM_ERROR: 

156 redirect = reverse(self.route_system_error) 

157 else: 

158 redirect = reverse(self.route_server_error) 

159 

160 data = {"redirect": redirect} 

161 return JsonResponse(data) 

162 

163 def _request_registration(self, request: HttpRequest, switchio_config: SwitchioConfig, session: Session) -> JsonResponse: 

164 response = request_registration(request, switchio_config, self.route_redirect) 

165 

166 if response.status is Status.SUCCESS: 

167 registration = response.registration 

168 session.registration_id = registration.regId 

169 session.gateway_url = registration.gtwUrl 

170 

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) 

176 

177 if response.status is Status.SYSTEM_ERROR: 

178 redirect = reverse(self.route_system_error) 

179 else: 

180 redirect = reverse(self.route_server_error) 

181 

182 data = {"redirect": redirect} 

183 return JsonResponse(data) 

184 

185 def _gateway_url_response(self, session: Session): 

186 data = {"gateway_url": session.gateway_url} 

187 return JsonResponse(data)