Coverage for benefits / enrollment_switchio / models.py: 97%

56 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-13 19:36 +0000

1import logging 

2 

3from django.core.exceptions import ValidationError 

4from django.db import models 

5 

6from benefits.core.models import EnrollmentGroup, Environment, SecretNameField, SystemName, TransitProcessorConfig 

7from benefits.secrets import get_secret_by_name 

8 

9logger = logging.getLogger(__name__) 

10 

11 

12class SwitchioGroupIDs: 

13 # SystemName.name: Switchio group ID 

14 MEDICARE = "MEDICARE" 

15 CALFRESH = "LOW_INCOME" 

16 OLDER_ADULT = "OLDER_ADULT" 

17 VETERAN = "VETERAN" 

18 # We have no Switchio agencies with Agency Card flows, but this is needed for testing. 

19 COURTESY_CARD = "AGENCY_CARD" 

20 

21 

22class SwitchioConfig(TransitProcessorConfig): 

23 """Configuration for connecting to Switchio, an entity that applies transit agency fare rules to rider transactions.""" 

24 

25 tokenization_api_key = models.TextField( 

26 help_text="The API key used to access the Switchio API for tokenization.", default="", blank=True 

27 ) 

28 tokenization_api_secret_name = SecretNameField( 

29 help_text="The name of the secret containing the api_secret value used to access the Switchio API for tokenization.", # noqa: E501 

30 default="", 

31 blank=True, 

32 ) 

33 pto_id = models.PositiveIntegerField( 

34 help_text="The Public Transport Operator ID to use with the Switchio API for enrollment.", 

35 default=0, 

36 blank=True, 

37 ) 

38 

39 @property 

40 def tokenization_api_base_url(self): 

41 return get_secret_by_name("switchio-tokenization-api-base-url") 

42 

43 @property 

44 def enrollment_api_base_url(self): 

45 return get_secret_by_name("switchio-enrollment-api-base-url") 

46 

47 @property 

48 def enrollment_api_authorization_header(self): 

49 return get_secret_by_name("switchio-enrollment-api-authorization-header") 

50 

51 @property 

52 def tokenization_api_secret(self): 

53 secret_field = self._meta.get_field("tokenization_api_secret_name") 

54 return secret_field.secret_value(self) 

55 

56 @property 

57 def client_certificate_data(self): 

58 """This SwitchioConfig's client certificate as a string.""" 

59 if self.environment == Environment.DEV.value: 59 ↛ 66line 59 didn't jump to line 66 because the condition on line 59 was always true

60 # Special case to handle un-purgeable cert in Azure dev env Key Vault with the desired `switchio-client-cert` name 

61 # See: https://cal-itp.slack.com/archives/C037Y3UE71P/p1776806316220499 

62 # Also affects local setup using standard fixtures with secrets 

63 # TODO: Remove this special case when the deleted cert is automatically purged on July 20, 2026 

64 return get_secret_by_name("switchio-int-client-cert") 

65 

66 return get_secret_by_name("switchio-client-cert") 

67 

68 @property 

69 def ca_certificate_data(self): 

70 """This SwitchioConfig's CA certificate as a string.""" 

71 return get_secret_by_name("switchio-ca-cert") 

72 

73 @property 

74 def private_key_data(self): 

75 """This SwitchioConfig's private key as a string.""" 

76 return get_secret_by_name("switchio-private-key") 

77 

78 def clean(self): 

79 field_errors = {} 

80 

81 if self.pk and self.transitagency_set and any([agency.active for agency in self.transitagency_set.all()]): 

82 message = "This field is required when this configuration is referenced by an active transit agency." 

83 needed = dict( 

84 tokenization_api_key=self.tokenization_api_key, 

85 tokenization_api_secret_name=self.tokenization_api_secret_name, 

86 pto_id=self.pto_id, 

87 ) 

88 field_errors.update({k: ValidationError(message) for k, v in needed.items() if not v}) 

89 

90 if field_errors: 

91 raise ValidationError(field_errors) 

92 

93 

94class SwitchioGroup(EnrollmentGroup): 

95 

96 @property 

97 def group_id(self): 

98 """Get the Switchio group ID, which is the same for all agencies for a given flow. 

99 

100 Returns the value of the attribute on SwitchioGroupIDs whose attribute name 

101 matches the one in SystemName that's used by this group's enrollment flow. 

102 """ 

103 return getattr(SwitchioGroupIDs, SystemName(self.enrollment_flow.system_name).name, None) 

104 

105 @staticmethod 

106 def by_id(id): 

107 """Get a SwitchioGroup instance by its ID.""" 

108 logger.debug(f"Get {SwitchioGroup.__name__} by id: {id}") 

109 return SwitchioGroup.objects.get(id=id)