Coverage for benefits/enrollment_littlepay/views.py: 90%

78 statements  

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

1import json 

2import logging 

3 

4import sentry_sdk 

5from django.http import JsonResponse 

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_littlepay.enrollment import enroll, request_card_tokenization_access 

15from benefits.enrollment_littlepay.session import Session 

16 

17from .routes import routes 

18 

19logger = logging.getLogger(__name__) 

20 

21 

22class TokenView(EligibleSessionRequiredMixin, View): 

23 """View handler for the card tokenization access token.""" 

24 

25 enrollment_method = models.EnrollmentMethods.SELF_SERVICE 

26 route_system_error = routes.ENROLLMENT_SYSTEM_ERROR 

27 route_server_error = routes.SERVER_ERROR 

28 

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

30 session = Session(request) 

31 

32 if not session.access_token_valid(): 

33 response = request_card_tokenization_access(request) 

34 

35 if response.status is Status.SUCCESS: 

36 session.access_token = response.access_token 

37 session.access_token_expiry = response.expires_at 

38 elif response.status is Status.SYSTEM_ERROR or response.status is Status.EXCEPTION: 38 ↛ 51line 38 didn't jump to line 51 because the condition on line 38 was always true

39 logger.debug("Error occurred while requesting access token", exc_info=response.exception) 

40 sentry_sdk.capture_exception(response.exception) 

41 analytics.failed_pretokenization_request(request, "littlepay", response.status_code, self.enrollment_method) 

42 

43 if response.status is Status.SYSTEM_ERROR: 

44 redirect = reverse(self.route_system_error) 

45 else: 

46 redirect = reverse(self.route_server_error) 

47 

48 data = {"redirect": redirect} 

49 return JsonResponse(data) 

50 

51 data = {"token": session.access_token} 

52 return JsonResponse(data) 

53 

54 

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

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

57 

58 enrollment_method = models.EnrollmentMethods.SELF_SERVICE 

59 form_class = forms.CardTokenizeSuccessForm 

60 route_enrollment_success = routes.ENROLLMENT_SUCCESS 

61 route_enrollment_retry = routes.ENROLLMENT_RETRY 

62 route_reenrollment_error = routes.ENROLLMENT_REENROLLMENT_ERROR 

63 route_server_error = routes.SERVER_ERROR 

64 route_system_error = routes.ENROLLMENT_SYSTEM_ERROR 

65 route_tokenize_success = routes.ENROLLMENT_LITTLEPAY_INDEX 

66 template_name = "enrollment_littlepay/index.html" 

67 

68 def get_context_data(self, **kwargs): 

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

70 

71 request = self.request 

72 agency = self.agency 

73 

74 tokenize_retry_form = forms.CardTokenizeFailForm(self.route_enrollment_retry, "form-card-tokenize-fail-retry") 

75 tokenize_server_error_form = forms.CardTokenizeFailForm( 

76 self.route_server_error, "form-card-tokenize-fail-server-error" 

77 ) 

78 tokenize_system_error_form = forms.CardTokenizeFailForm( 

79 self.route_system_error, "form-card-tokenize-fail-system-error" 

80 ) 

81 tokenize_success_form = forms.CardTokenizeSuccessForm( 

82 action_url=self.route_tokenize_success, auto_id=True, label_suffix="" 

83 ) 

84 

85 context.update( 

86 { 

87 "forms": [tokenize_retry_form, tokenize_server_error_form, tokenize_system_error_form, tokenize_success_form], 

88 "cta_button": "tokenize_card", 

89 "enrollment_method": self.enrollment_method, 

90 "token_field": "card_token", 

91 "form_retry": tokenize_retry_form.id, 

92 "form_server_error": tokenize_server_error_form.id, 

93 "form_success": tokenize_success_form.id, 

94 "form_system_error": tokenize_system_error_form.id, 

95 "overlay_language": self._get_overlay_language(request.LANGUAGE_CODE), 

96 "card_schemes": json.dumps(agency.supported_card_schemes), 

97 "routes": routes.to_dict(), 

98 } 

99 ) 

100 

101 match agency.transit_processor.environment: 

102 case models.Environment.TEST.value: 102 ↛ 105line 102 didn't jump to line 105 because the pattern on line 102 always matched

103 url = "https://verify.qa.littlepay.com/assets/js/littlepay.min.js" 

104 card_tokenize_env = "https://verify.qa.littlepay.com" 

105 case models.Environment.PROD.value: 

106 url = "https://verify.littlepay.com/assets/js/littlepay.min.js" 

107 card_tokenize_env = "https://verify.littlepay.com" 

108 case _: 

109 raise ValueError("Unrecognized environment value") 

110 

111 context["transit_processor"] = dict( 

112 name="Littlepay", website="https://littlepay.com", card_tokenize_url=url, card_tokenize_env=card_tokenize_env 

113 ) 

114 

115 return context 

116 

117 def _get_overlay_language(self, django_language_code): 

118 """Given a Django language code, return the corresponding language code to use with Littlepay's overlay.""" 

119 # mapping from Django's I18N LANGUAGE_CODE to Littlepay's overlay language code 

120 overlay_language = {"en": "en", "es": "es-419"}.get(django_language_code, "en") 

121 return overlay_language 

122 

123 def _get_verified_by(self): 

124 return self.flow.eligibility_verifier 

125 

126 def form_valid(self, form): 

127 card_token = form.cleaned_data.get("card_token") 

128 status, exception, funding_source = enroll(self.request, card_token) 

129 

130 return handle_enrollment_results( 

131 request=self.request, 

132 status=status, 

133 verified_by=self._get_verified_by(), 

134 exception=exception, 

135 enrollment_method=self.enrollment_method, 

136 route_reenrollment_error=self.route_reenrollment_error, 

137 route_success=self.route_enrollment_success, 

138 route_system_error=self.route_system_error, 

139 card_category=funding_source.card_category, 

140 card_scheme=funding_source.card_scheme, 

141 ) 

142 

143 def form_invalid(self, form): 

144 raise Exception("Invalid card token form")