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
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-08 19:50 +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
17from .routes import routes
19logger = logging.getLogger(__name__)
22class TokenView(EligibleSessionRequiredMixin, View):
23 """View handler for the card tokenization access token."""
25 enrollment_method = models.EnrollmentMethods.SELF_SERVICE
26 route_system_error = routes.ENROLLMENT_SYSTEM_ERROR
27 route_server_error = routes.SERVER_ERROR
29 def get(self, request, *args, **kwargs):
30 session = Session(request)
32 if not session.access_token_valid():
33 response = request_card_tokenization_access(request)
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)
43 if response.status is Status.SYSTEM_ERROR:
44 redirect = reverse(self.route_system_error)
45 else:
46 redirect = reverse(self.route_server_error)
48 data = {"redirect": redirect}
49 return JsonResponse(data)
51 data = {"token": session.access_token}
52 return JsonResponse(data)
55class IndexView(AgencySessionRequiredMixin, EligibleSessionRequiredMixin, IndexContextMixin, FormView):
56 """View for the enrollment landing page."""
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"
68 def get_context_data(self, **kwargs):
69 context = super().get_context_data(**kwargs)
71 request = self.request
72 agency = self.agency
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 )
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 )
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")
111 context["transit_processor"] = dict(
112 name="Littlepay", website="https://littlepay.com", card_tokenize_url=url, card_tokenize_env=card_tokenize_env
113 )
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, funding_source = 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 card_category=funding_source.card_category,
140 card_scheme=funding_source.card_scheme,
141 )
143 def form_invalid(self, form):
144 raise Exception("Invalid card token form")