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

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 

22from benefits.routes import routes 

23 

24logger = logging.getLogger(__name__) 

25 

26 

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

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

29 

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" 

39 

40 def get_context_data(self, **kwargs): 

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

42 

43 request = self.request 

44 

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 

63 

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 

69 

70 def _get_verified_by(self): 

71 return self.flow.eligibility_verifier 

72 

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

74 session = Session(request) 

75 switchio_config = self.agency.switchio_config 

76 

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) 

86 

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) 

96 

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) 

101 

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

103 

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

109 

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 ) 

123 

124 

125class GatewayUrlView(AgencySessionRequiredMixin, EligibleSessionRequiredMixin, View): 

126 """View for the tokenization gateway registration""" 

127 

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 

132 

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

134 session = Session(request) 

135 switchio_config = self.agency.switchio_config 

136 

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) 

141 

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) 

152 

153 if response.status is Status.SYSTEM_ERROR: 

154 redirect = reverse(self.route_system_error) 

155 else: 

156 redirect = reverse(self.route_server_error) 

157 

158 data = {"redirect": redirect} 

159 return JsonResponse(data) 

160 

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

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

163 

164 if response.status is Status.SUCCESS: 

165 registration = response.registration 

166 session.registration_id = registration.regId 

167 session.gateway_url = registration.gtwUrl 

168 

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) 

174 

175 if response.status is Status.SYSTEM_ERROR: 

176 redirect = reverse(self.route_system_error) 

177 else: 

178 redirect = reverse(self.route_server_error) 

179 

180 data = {"redirect": redirect} 

181 return JsonResponse(data) 

182 

183 def _gateway_url_response(self, session: Session): 

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

185 return JsonResponse(data)