Coverage for benefits/enrollment_littlepay/views.py: 90%
80 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-10 16:52 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-10 16:52 +0000
1import json
2import logging
4from django.http import JsonResponse
5from django.urls import reverse
6from django.views.generic import FormView, View
7import sentry_sdk
9from benefits.routes import routes
10from benefits.core import models
11from benefits.core.mixins import AgencySessionRequiredMixin, EligibleSessionRequiredMixin, FlowSessionRequiredMixin
13from benefits.enrollment import analytics, forms
14from benefits.enrollment.enrollment import Status, handle_enrollment_results
15from benefits.enrollment_littlepay.enrollment import enroll, request_card_tokenization_access
16from benefits.enrollment_littlepay.session import Session
18logger = logging.getLogger(__name__)
21class TokenView(EligibleSessionRequiredMixin, View):
22 """View handler for the card tokenization access token."""
24 enrollment_method = models.EnrollmentMethods.DIGITAL
25 route_system_error = routes.ENROLLMENT_SYSTEM_ERROR
26 route_server_error = routes.SERVER_ERROR
28 def get(self, request, *args, **kwargs):
29 session = Session(request)
31 if not session.access_token_valid():
32 response = request_card_tokenization_access(request)
34 if response.status is Status.SUCCESS:
35 session.access_token = response.access_token
36 session.access_token_expiry = response.expires_at
37 elif response.status is Status.SYSTEM_ERROR or response.status is Status.EXCEPTION: 37 ↛ 50line 37 didn't jump to line 50 because the condition on line 37 was always true
38 logger.debug("Error occurred while requesting access token", exc_info=response.exception)
39 sentry_sdk.capture_exception(response.exception)
40 analytics.failed_pretokenization_request(request, response.status_code, self.enrollment_method)
42 if response.status is Status.SYSTEM_ERROR:
43 redirect = reverse(self.route_system_error)
44 else:
45 redirect = reverse(self.route_server_error)
47 data = {"redirect": redirect}
48 return JsonResponse(data)
50 data = {"token": session.access_token}
51 return JsonResponse(data)
54class IndexView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, EligibleSessionRequiredMixin, FormView):
55 """View for the enrollment landing page."""
57 enrollment_method = models.EnrollmentMethods.DIGITAL
58 form_class = forms.CardTokenizeSuccessForm
59 route_enrollment_success = routes.ENROLLMENT_SUCCESS
60 route_enrollment_retry = routes.ENROLLMENT_RETRY
61 route_reenrollment_error = routes.ENROLLMENT_REENROLLMENT_ERROR
62 route_server_error = routes.SERVER_ERROR
63 route_system_error = routes.ENROLLMENT_SYSTEM_ERROR
64 route_tokenize_success = routes.ENROLLMENT_LITTLEPAY_INDEX
65 template_name = "enrollment_littlepay/index.html"
67 def get_context_data(self, **kwargs):
68 request = self.request
69 agency = self.agency
70 flow = self.flow
72 tokenize_retry_form = forms.CardTokenizeFailForm(self.route_enrollment_retry, "form-card-tokenize-fail-retry")
73 tokenize_server_error_form = forms.CardTokenizeFailForm(
74 self.route_server_error, "form-card-tokenize-fail-server-error"
75 )
76 tokenize_system_error_form = forms.CardTokenizeFailForm(
77 self.route_system_error, "form-card-tokenize-fail-system-error"
78 )
79 tokenize_success_form = forms.CardTokenizeSuccessForm(
80 action_url=self.route_tokenize_success, auto_id=True, label_suffix=""
81 )
83 context = {
84 "forms": [tokenize_retry_form, tokenize_server_error_form, tokenize_system_error_form, tokenize_success_form],
85 "cta_button": "tokenize_card",
86 "enrollment_method": self.enrollment_method,
87 "token_field": "card_token",
88 "form_retry": tokenize_retry_form.id,
89 "form_server_error": tokenize_server_error_form.id,
90 "form_success": tokenize_success_form.id,
91 "form_system_error": tokenize_system_error_form.id,
92 "overlay_language": self._get_overlay_language(request.LANGUAGE_CODE),
93 "card_schemes": json.dumps(agency.supported_card_schemes),
94 }
96 enrollment_index_context_dict = flow.enrollment_index_context
98 match agency.littlepay_config.environment:
99 case models.Environment.QA.value: 99 ↛ 102line 99 didn't jump to line 102 because the pattern on line 99 always matched
100 url = "https://verify.qa.littlepay.com/assets/js/littlepay.min.js"
101 card_tokenize_env = "https://verify.qa.littlepay.com"
102 case models.Environment.PROD.value:
103 url = "https://verify.littlepay.com/assets/js/littlepay.min.js"
104 card_tokenize_env = "https://verify.littlepay.com"
105 case _:
106 raise ValueError("Unrecognized environment value")
108 transit_processor_context = dict(
109 name="Littlepay", website="https://littlepay.com", card_tokenize_url=url, card_tokenize_env=card_tokenize_env
110 )
112 enrollment_index_context_dict["transit_processor"] = transit_processor_context
113 context.update(enrollment_index_context_dict)
115 return context
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
123 def _get_verified_by(self):
124 return self.flow.eligibility_verifier
126 def form_valid(self, form):
127 card_token = form.cleaned_data.get("card_token")
128 status, exception = enroll(self.request, card_token)
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 )
141 def form_invalid(self, form):
142 raise Exception("Invalid card token form")