Coverage for benefits/in_person/views.py: 98%

120 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-08 19:50 +0000

1import logging 

2 

3from django.shortcuts import redirect 

4from django.urls import reverse 

5from django.views.generic import FormView, TemplateView 

6 

7from benefits.core import models, session 

8from benefits.core.mixins import AgencySessionRequiredMixin 

9from benefits.core.models.transit import TransitAgency 

10from benefits.core.views import AdditionalAgenciesView as SelfServiceAdditionalAgenciesView 

11from benefits.eligibility import analytics as eligibility_analytics 

12from benefits.enrollment.views import ( 

13 IndexView as SelfServiceEnrollmentIndexView, 

14 ReenrollmentErrorView as SelfServiceReenrollmentErrorView, 

15 RetryView as SelfServiceRetryView, 

16 SuccessView as SelfServiceSuccessView, 

17 SystemErrorView as SelfServiceSystemErrorView, 

18) 

19from benefits.enrollment_littlepay.routes import routes as littlepay_routes 

20from benefits.enrollment_littlepay.session import Session as LittlepaySession 

21from benefits.enrollment_littlepay.views import IndexView as LittlepayIndexView, TokenView 

22from benefits.enrollment_switchio.routes import routes as switchio_routes 

23from benefits.enrollment_switchio.session import Session as SwitchioSession 

24from benefits.enrollment_switchio.views import GatewayUrlView, IndexView as SwitchioIndexView 

25from benefits.in_person import forms, mixins 

26from benefits.routes import routes 

27 

28logger = logging.getLogger(__name__) 

29 

30 

31class AdditionalAgenciesView(mixins.CommonContextMixin, SelfServiceAdditionalAgenciesView): 

32 """View handler for showing the list of agencies the customer will be enrolled at (if more than one).""" 

33 

34 template_name = "in_person/additional-agencies.html" 

35 only_active_agencies = False 

36 

37 

38class EligibilityView(mixins.CommonContextMixin, FormView): 

39 """CBV for the in-person eligibility flow selection form.""" 

40 

41 template_name = "in_person/eligibility.html" 

42 form_class = forms.InPersonEligibilityForm 

43 

44 def dispatch(self, request, *args, **kwargs): 

45 """Initialize session state before handling the request.""" 

46 

47 LittlepaySession(request, reset=True) 

48 SwitchioSession(request, reset=True) 

49 

50 agency = session.agency(request) 

51 if not agency: 

52 agency = TransitAgency.for_user(request.user) 

53 session.update(request, agency=agency) 

54 self.agency = agency 

55 return super().dispatch(request, *args, **kwargs) 

56 

57 def get_form_kwargs(self): 

58 """Return the keyword arguments for instantiating the form.""" 

59 

60 kwargs = super().get_form_kwargs() 

61 kwargs["agency"] = self.agency 

62 return kwargs 

63 

64 def form_valid(self, form): 

65 """If the form is valid, set enrollment flow, eligible session, and redirect.""" 

66 

67 flow_id = form.cleaned_data.get("flow") 

68 flow = models.EnrollmentFlow.objects.get(id=flow_id) 

69 session.update(self.request, flow=flow, eligible=True) 

70 eligibility_analytics.selected_flow(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON) 

71 eligibility_analytics.started_eligibility(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON) 

72 eligibility_analytics.returned_success(self.request, flow, enrollment_method=models.EnrollmentMethods.IN_PERSON) 

73 return redirect(routes.IN_PERSON_ENROLLMENT) 

74 

75 

76class LittlepayTokenView(TokenView): 

77 """View handler for the enrollment auth token.""" 

78 

79 enrollment_method = models.EnrollmentMethods.IN_PERSON 

80 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR 

81 route_server_error = routes.IN_PERSON_SERVER_ERROR 

82 

83 

84class EnrollmentView(SelfServiceEnrollmentIndexView): 

85 

86 route_origin = routes.IN_PERSON_ENROLLMENT 

87 

88 def get_redirect_url(self, *args, **kwargs): 

89 route_name = self.agency.in_person_enrollment_index_route 

90 return reverse(route_name) 

91 

92 

93class LittlepayEnrollmentView(mixins.CommonContextMixin, LittlepayIndexView): 

94 """View handler for the in-person enrollment page.""" 

95 

96 enrollment_method = models.EnrollmentMethods.IN_PERSON 

97 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS 

98 route_enrollment_retry = routes.IN_PERSON_ENROLLMENT_RETRY 

99 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR 

100 route_server_error = routes.IN_PERSON_SERVER_ERROR 

101 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR 

102 route_tokenize_success = littlepay_routes.IN_PERSON_ENROLLMENT_LITTLEPAY_INDEX 

103 template_name = "in_person/enrollment/index_littlepay.html" 

104 

105 def _get_verified_by(self): 

106 return f"{self.request.user.first_name} {self.request.user.last_name}" 

107 

108 

109class ReenrollmentErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, SelfServiceReenrollmentErrorView): 

110 """View handler for a re-enrollment attempt that is not yet within the re-enrollment window.""" 

111 

112 template_name = "in_person/enrollment/reenrollment_error.html" 

113 

114 def get_context_data(self, **kwargs): 

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

116 

117 context["flow_label"] = self.flow.label 

118 return context 

119 

120 

121class RetryView(mixins.CommonContextMixin, SelfServiceRetryView): 

122 """View handler for card verification failure.""" 

123 

124 template_name = "in_person/enrollment/retry.html" 

125 enrollment_method = models.EnrollmentMethods.IN_PERSON 

126 

127 

128class SystemErrorView(mixins.CommonContextMixin, SelfServiceSystemErrorView): 

129 """View handler for an enrollment system error.""" 

130 

131 template_name = "in_person/enrollment/system_error.html" 

132 

133 def get_origin_url(self): 

134 return reverse(routes.ADMIN_INDEX) 

135 

136 

137class ServerErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, TemplateView): 

138 """View handler for errors caused by a misconfiguration or bad request.""" 

139 

140 template_name = "in_person/enrollment/server_error.html" 

141 

142 def post(self, request, *args, **kwargs): 

143 # the Javascript in in_person/index_littlepay.html sends a form POST to this view 

144 # rather than implementing this view as a FormView, which requires instantiating the 

145 # enrollment.forms.CardTokenizeFailForm, we implement post() to simply return the template via get() 

146 # we thus avoid interfering with the view's lifecycle and dispatch() method 

147 return super().get(request, *args, **kwargs) 

148 

149 

150class SuccessView(mixins.CommonContextMixin, AgencySessionRequiredMixin, SelfServiceSuccessView): 

151 """View handler for the final success page.""" 

152 

153 template_name = "in_person/enrollment/success.html" 

154 only_active_agencies = False 

155 

156 def get_context_data(self, **kwargs): 

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

158 

159 # Check SelfServiceSuccessView's agency_short_names to see if we have a group situation 

160 if context["agency_short_names"]: 

161 context["success_message"] = ( 

162 "This rider can now use their contactless card to automatically receive a reduced fare " 

163 "when they tap-to-ride at the following transit providers:" 

164 ) 

165 else: 

166 context["success_message"] = ( 

167 "This rider can now use their contactless card to automatically receive a reduced fare " 

168 "when they tap-to-ride." 

169 ) 

170 

171 return context 

172 

173 

174class SwitchioGatewayUrlView(GatewayUrlView): 

175 enrollment_method = models.EnrollmentMethods.IN_PERSON 

176 route_redirect = switchio_routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX 

177 route_server_error = routes.IN_PERSON_SERVER_ERROR 

178 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR 

179 

180 

181class SwitchioEnrollmentIndexView(mixins.CommonContextMixin, SwitchioIndexView): 

182 enrollment_method = models.EnrollmentMethods.IN_PERSON 

183 form_class = forms.CardTokenizeSuccessForm 

184 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS 

185 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR 

186 route_retry = routes.IN_PERSON_ENROLLMENT_RETRY 

187 route_server_error = routes.IN_PERSON_SERVER_ERROR 

188 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR 

189 route_tokenize_success = switchio_routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX 

190 template_name = "in_person/enrollment/index_switchio.html" 

191 

192 def _get_verified_by(self): 

193 return f"{self.request.user.first_name} {self.request.user.last_name}" 

194 

195 def get_context_data(self, **kwargs): 

196 """Add in-person specific context data.""" 

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

198 

199 if self.request.GET.get("state") == "tokenize": 

200 message = "Registering this contactless card for reduced fares..." 

201 else: 

202 message = "Connecting with payment processor..." 

203 

204 context.update({"loading_message": message}) 

205 return context 

206 

207 def get(self, request, *args, **kwargs): 

208 if request.GET.get("error") == "canceled": 208 ↛ 213line 208 didn't jump to line 213 because the condition on line 208 was always true

209 # the user clicked the "Back" button on the Switchio tokenization gateway 

210 # send them back to the Admin index, similar to the Littlepay cancel button 

211 return redirect(routes.ADMIN_INDEX) 

212 

213 return super().get(request, *args, **kwargs)