Coverage for functions \ flipdare \ payments \ core \ stripe_util.py: 89%

74 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-05-08 12:22 +1000

1#!/usr/bin/env python 

2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved. 

3# 

4# This file is part of Flipdare's proprietary software and contains 

5# confidential and copyrighted material. Unauthorised copying, 

6# modification, distribution, or use of this file is strictly 

7# prohibited without prior written permission from Flipdare Pty Ltd. 

8# 

9# This software includes third-party components licensed under MIT, 

10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details. 

11# 

12from flipdare.app_log import LOG 

13from flipdare.generated import StripeAccountType, StripeCountryCode, StripeCurrencyCode 

14 

15from flipdare.app_globals import is_letters_present, is_text_present 

16from nameparser import HumanName 

17from flipdare.constants import ( 

18 STRIPE_FALLBACK_LAST_STR, 

19 STRIPE_FALLBACK_PREFIX_STR, 

20) 

21 

22__all__ = ["StripeUtil"] 

23 

24 

25class StripeUtil: 

26 __slots__ = () 

27 

28 @staticmethod 

29 def cvt_account_type(account_type: str | None) -> StripeAccountType | None: 

30 if account_type is None: 

31 return None 

32 

33 try: 

34 return StripeAccountType(account_type.lower()) 

35 except ValueError: 

36 msg = f"Invalid account type {account_type}.." 

37 LOG().warning(msg) 

38 return None 

39 

40 @staticmethod 

41 def cvt_stripe_currency( 

42 currency: StripeCurrencyCode | str | None, 

43 ) -> StripeCurrencyCode | None: 

44 if currency is None: 

45 return None 

46 

47 if isinstance(currency, StripeCurrencyCode): 

48 return currency 

49 

50 try: 

51 return StripeCurrencyCode(currency.upper()) 

52 except ValueError: 

53 msg = f"Invalid currency code {currency}.." 

54 LOG().warning(msg) 

55 return None 

56 

57 @staticmethod 

58 def cvt_stripe_country(country: StripeCountryCode | str | None) -> StripeCountryCode | None: 

59 if country is None: 

60 return None 

61 

62 if isinstance(country, StripeCountryCode): 

63 return country 

64 

65 try: 

66 return StripeCountryCode(country.upper()) 

67 except ValueError: 

68 msg = f"Invalid country code {country}.." 

69 LOG().warning(msg) 

70 return None 

71 

72 @staticmethod 

73 def get_first_last_name(name: str) -> tuple[str, str] | None: 

74 if not is_text_present(name): 

75 return None 

76 

77 parser = HumanName(name) 

78 first_name = parser.first 

79 last_name = parser.last 

80 if len(first_name) > 1 and len(last_name) > 1: 

81 return first_name, last_name 

82 

83 return None 

84 

85 @staticmethod 

86 def get_name_tokens(name: str, email: str) -> tuple[str, str]: 

87 """ 

88 Extract first/last name (min 1 char each) for invoice prefix generation. 

89 

90 Strategy: 

91 1. Try HumanName parser for proper name parsing 

92 2. Split name by whitespace and use first two parts 

93 3. Fallback to email local part (split in half) 

94 4. Final fallback: "User" + first letter of email 

95 """ 

96 # Try HumanName parser (handles "John Doe", "Doe, John", etc.) 

97 if is_letters_present(name): 

98 parsed = StripeUtil.get_first_last_name(name) 

99 if parsed is not None: 

100 return parsed[0].capitalize(), parsed[1].capitalize() 

101 

102 # Simple whitespace split fallback 

103 parts = name.split() 

104 if len(parts) >= 2: # noqa: PLR2004 

105 return parts[0], parts[1] 

106 if len(parts) == 1 and len(parts[0]) >= 2: # noqa: PLR2004 

107 # Single word name: split in half 

108 mid = len(parts[0]) // 2 

109 return parts[0][:mid].capitalize(), parts[0][mid:].capitalize() 

110 

111 # Email fallback: use local part (before @) 

112 local = email.split("@", maxsplit=1)[0] if "@" in email else email 

113 # see if there is a '.' in the local part, if so split on that 

114 if "." in local: 

115 subparts = local.split(".") 

116 if len(subparts) >= 2 and all(len(s) >= 1 for s in subparts[:2]): # noqa: PLR2004 

117 return subparts[0].capitalize(), subparts[1].capitalize() 

118 

119 # Filter to alphabetic chars only 

120 alpha_chars = "".join(c for c in local if c.isalpha()) 

121 

122 if len(alpha_chars) >= 2: # noqa: PLR2004 

123 # just split the alphabetic chars in half 

124 mid = len(alpha_chars) // 2 

125 return alpha_chars[:mid].capitalize(), alpha_chars[mid:].capitalize() 

126 

127 # Final fallback: ensure we always return valid strings 

128 return STRIPE_FALLBACK_PREFIX_STR, ( 

129 alpha_chars[0].capitalize() if alpha_chars else STRIPE_FALLBACK_LAST_STR 

130 )