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

107 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-22 19:08 +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.models.transit import TransitAgency 

8from benefits.core import models, session 

9from benefits.core.mixins import AgencySessionRequiredMixin 

10from benefits.eligibility import analytics as eligibility_analytics 

11from benefits.enrollment.views import ( 

12 IndexView, 

13 ReenrollmentErrorView as DigitalReenrollmentErrorView, 

14 RetryView as DigitalRetryView, 

15 SystemErrorView as DigitalSystemErrorView, 

16 SuccessView as DigitalSuccessView, 

17) 

18from benefits.enrollment_littlepay.session import Session as LittlepaySession 

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

20from benefits.enrollment_switchio.session import Session as SwitchioSession 

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

22 

23from benefits.in_person import forms, mixins 

24from benefits.routes import routes 

25 

26logger = logging.getLogger(__name__) 

27 

28 

29class EligibilityView(mixins.CommonContextMixin, FormView): 

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

31 

32 template_name = "in_person/eligibility.html" 

33 form_class = forms.InPersonEligibilityForm 

34 

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

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

37 

38 LittlepaySession(request, reset=True) 

39 SwitchioSession(request, reset=True) 

40 

41 agency = session.agency(request) 

42 if not agency: 

43 agency = TransitAgency.for_user(request.user) 

44 session.update(request, agency=agency) 

45 self.agency = agency 

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

47 

48 def get_form_kwargs(self): 

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

50 

51 kwargs = super().get_form_kwargs() 

52 kwargs["agency"] = self.agency 

53 return kwargs 

54 

55 def form_valid(self, form): 

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

57 

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

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

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

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

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

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

64 return redirect(routes.IN_PERSON_ENROLLMENT) 

65 

66 

67class LittlepayTokenView(TokenView): 

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

69 

70 enrollment_method = models.EnrollmentMethods.IN_PERSON 

71 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR 

72 route_server_error = routes.IN_PERSON_SERVER_ERROR 

73 

74 

75class EnrollmentView(IndexView): 

76 

77 route_origin = routes.IN_PERSON_ENROLLMENT 

78 

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

80 route_name = self.agency.in_person_enrollment_index_route 

81 return reverse(route_name) 

82 

83 

84class LittlepayEnrollmentView(mixins.CommonContextMixin, LittlepayIndexView): 

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

86 

87 enrollment_method = models.EnrollmentMethods.IN_PERSON 

88 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS 

89 route_enrollment_retry = routes.IN_PERSON_ENROLLMENT_RETRY 

90 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR 

91 route_server_error = routes.IN_PERSON_SERVER_ERROR 

92 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR 

93 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_LITTLEPAY_INDEX 

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

95 

96 def _get_verified_by(self): 

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

98 

99 

100class ReenrollmentErrorView(mixins.CommonContextMixin, AgencySessionRequiredMixin, DigitalReenrollmentErrorView): 

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

102 

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

104 

105 def get_context_data(self, **kwargs): 

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

107 

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

109 return context 

110 

111 

112class RetryView(mixins.CommonContextMixin, DigitalRetryView): 

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

114 

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

116 enrollment_method = models.EnrollmentMethods.IN_PERSON 

117 

118 

119class SystemErrorView(mixins.CommonContextMixin, DigitalSystemErrorView): 

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

121 

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

123 

124 def get_origin_url(self): 

125 return reverse(routes.ADMIN_INDEX) 

126 

127 

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

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

130 

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

132 

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

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

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

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

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

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

139 

140 

141class SuccessView(mixins.CommonContextMixin, AgencySessionRequiredMixin, DigitalSuccessView): 

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

143 

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

145 

146 

147class SwitchioGatewayUrlView(GatewayUrlView): 

148 enrollment_method = models.EnrollmentMethods.IN_PERSON 

149 route_redirect = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX 

150 route_server_error = routes.IN_PERSON_SERVER_ERROR 

151 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR 

152 

153 

154class SwitchioEnrollmentIndexView(mixins.CommonContextMixin, SwitchioIndexView): 

155 enrollment_method = models.EnrollmentMethods.IN_PERSON 

156 form_class = forms.CardTokenizeSuccessForm 

157 route_enrollment_success = routes.IN_PERSON_ENROLLMENT_SUCCESS 

158 route_reenrollment_error = routes.IN_PERSON_ENROLLMENT_REENROLLMENT_ERROR 

159 route_retry = routes.IN_PERSON_ENROLLMENT_RETRY 

160 route_server_error = routes.IN_PERSON_SERVER_ERROR 

161 route_system_error = routes.IN_PERSON_ENROLLMENT_SYSTEM_ERROR 

162 route_tokenize_success = routes.IN_PERSON_ENROLLMENT_SWITCHIO_INDEX 

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

164 

165 def _get_verified_by(self): 

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

167 

168 def get_context_data(self, **kwargs): 

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

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

171 

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

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

174 else: 

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

176 

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

178 return context 

179 

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

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

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

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

184 return redirect(routes.ADMIN_INDEX) 

185 

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