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

72 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-06 20:07 +0000

1import json 

2import logging 

3 

4from django.conf import settings 

5from django.http import JsonResponse 

6from django.urls import reverse 

7from django.views.generic import FormView, View 

8import sentry_sdk 

9 

10from benefits.routes import routes 

11from benefits.core import models, session 

12from benefits.core.mixins import EligibleSessionRequiredMixin 

13 

14from benefits.enrollment import analytics, forms 

15from benefits.enrollment.enrollment import Status, handle_enrollment_results 

16from benefits.enrollment_littlepay.enrollment import enroll, request_card_tokenization_access 

17from benefits.enrollment_littlepay.session import Session 

18 

19logger = logging.getLogger(__name__) 

20 

21 

22class TokenView(EligibleSessionRequiredMixin, View): 

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

24 

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

26 session = Session(request) 

27 

28 if not session.access_token_valid(): 

29 response = request_card_tokenization_access(request) 

30 

31 if response.status is Status.SUCCESS: 

32 session.access_token = response.access_token 

33 session.access_token_expiry = response.expires_at 

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

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

36 sentry_sdk.capture_exception(response.exception) 

37 analytics.failed_access_token_request(request, response.status_code) 

38 

39 if response.status is Status.SYSTEM_ERROR: 

40 redirect = reverse(routes.ENROLLMENT_SYSTEM_ERROR) 

41 else: 

42 redirect = reverse(routes.SERVER_ERROR) 

43 

44 data = {"redirect": redirect} 

45 return JsonResponse(data) 

46 

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

48 return JsonResponse(data) 

49 

50 

51class IndexView(EligibleSessionRequiredMixin, FormView): 

52 template_name = "enrollment_littlepay/index.html" 

53 form_class = forms.CardTokenizeSuccessForm 

54 

55 def get_context_data(self, **kwargs): 

56 request = self.request 

57 agency = session.agency(request) 

58 flow = session.flow(request) 

59 

60 tokenize_retry_form = forms.CardTokenizeFailForm(routes.ENROLLMENT_RETRY, "form-card-tokenize-fail-retry") 

61 tokenize_server_error_form = forms.CardTokenizeFailForm(routes.SERVER_ERROR, "form-card-tokenize-fail-server-error") 

62 tokenize_system_error_form = forms.CardTokenizeFailForm( 

63 routes.ENROLLMENT_SYSTEM_ERROR, "form-card-tokenize-fail-system-error" 

64 ) 

65 tokenize_success_form = forms.CardTokenizeSuccessForm( 

66 action_url=routes.ENROLLMENT_LITTLEPAY_INDEX, auto_id=True, label_suffix="" 

67 ) 

68 

69 card_types = ["visa", "mastercard"] 

70 if settings.LITTLEPAY_ADDITIONAL_CARDTYPES: 

71 card_types.extend(["discover", "amex"]) 

72 

73 context = { 

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

75 "cta_button": "tokenize_card", 

76 "enrollment_method": models.EnrollmentMethods.DIGITAL, 

77 "token_field": "card_token", 

78 "form_retry": tokenize_retry_form.id, 

79 "form_server_error": tokenize_server_error_form.id, 

80 "form_success": tokenize_success_form.id, 

81 "form_system_error": tokenize_system_error_form.id, 

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

83 # convert the python list to a JSON string for use in JavaScript 

84 "card_types": json.dumps(card_types), 

85 } 

86 

87 enrollment_index_context_dict = flow.enrollment_index_context 

88 

89 match agency.littlepay_config.environment: 

90 case models.Environment.QA.value: 90 ↛ 93line 90 didn't jump to line 93 because the pattern on line 90 always matched

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

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

93 case models.Environment.PROD.value: 

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

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

96 case _: 

97 raise ValueError("Unrecognized environment value") 

98 

99 transit_processor_context = dict( 

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

101 ) 

102 

103 enrollment_index_context_dict["transit_processor"] = transit_processor_context 

104 context.update(enrollment_index_context_dict) 

105 

106 return context 

107 

108 def _get_overlay_language(self, django_language_code): 

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

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

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

112 return overlay_language 

113 

114 def form_valid(self, form): 

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

116 status, exception = enroll(self.request, card_token) 

117 

118 return handle_enrollment_results(self.request, status, exception) 

119 

120 def form_invalid(self, form): 

121 raise Exception("Invalid card token form")