Coverage for benefits / enrollment_littlepay / views.py: 90%
78 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +0000
1import json
2import logging
4import sentry_sdk
5from django.http import JsonResponse
6from django.urls import reverse
7from django.views.generic import FormView, View
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
16from benefits.routes import routes
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, "littlepay", 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, EligibleSessionRequiredMixin, IndexContextMixin, 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 context = super().get_context_data(**kwargs)
70 request = self.request
71 agency = self.agency
73 tokenize_retry_form = forms.CardTokenizeFailForm(self.route_enrollment_retry, "form-card-tokenize-fail-retry")
74 tokenize_server_error_form = forms.CardTokenizeFailForm(
75 self.route_server_error, "form-card-tokenize-fail-server-error"
76 )
77 tokenize_system_error_form = forms.CardTokenizeFailForm(
78 self.route_system_error, "form-card-tokenize-fail-system-error"
79 )
80 tokenize_success_form = forms.CardTokenizeSuccessForm(
81 action_url=self.route_tokenize_success, auto_id=True, label_suffix=""
82 )
84 context.update(
85 {
86 "forms": [tokenize_retry_form, tokenize_server_error_form, tokenize_system_error_form, tokenize_success_form],
87 "cta_button": "tokenize_card",
88 "enrollment_method": self.enrollment_method,
89 "token_field": "card_token",
90 "form_retry": tokenize_retry_form.id,
91 "form_server_error": tokenize_server_error_form.id,
92 "form_success": tokenize_success_form.id,
93 "form_system_error": tokenize_system_error_form.id,
94 "overlay_language": self._get_overlay_language(request.LANGUAGE_CODE),
95 "card_schemes": json.dumps(agency.supported_card_schemes),
96 }
97 )
99 match agency.littlepay_config.environment:
100 case models.Environment.TEST.value: 100 ↛ 103line 100 didn't jump to line 103 because the pattern on line 100 always matched
101 url = "https://verify.qa.littlepay.com/assets/js/littlepay.min.js"
102 card_tokenize_env = "https://verify.qa.littlepay.com"
103 case models.Environment.PROD.value:
104 url = "https://verify.littlepay.com/assets/js/littlepay.min.js"
105 card_tokenize_env = "https://verify.littlepay.com"
106 case _:
107 raise ValueError("Unrecognized environment value")
109 context["transit_processor"] = dict(
110 name="Littlepay", website="https://littlepay.com", card_tokenize_url=url, card_tokenize_env=card_tokenize_env
111 )
113 return context
115 def _get_overlay_language(self, django_language_code):
116 """Given a Django language code, return the corresponding language code to use with Littlepay's overlay."""
117 # mapping from Django's I18N LANGUAGE_CODE to Littlepay's overlay language code
118 overlay_language = {"en": "en", "es": "es-419"}.get(django_language_code, "en")
119 return overlay_language
121 def _get_verified_by(self):
122 return self.flow.eligibility_verifier
124 def form_valid(self, form):
125 card_token = form.cleaned_data.get("card_token")
126 status, exception, funding_source = enroll(self.request, card_token)
128 return handle_enrollment_results(
129 request=self.request,
130 status=status,
131 verified_by=self._get_verified_by(),
132 exception=exception,
133 enrollment_method=self.enrollment_method,
134 route_reenrollment_error=self.route_reenrollment_error,
135 route_success=self.route_enrollment_success,
136 route_system_error=self.route_system_error,
137 card_category=funding_source.card_category,
138 card_scheme=funding_source.card_scheme,
139 )
141 def form_invalid(self, form):
142 raise Exception("Invalid card token form")