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
« 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
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)
22__all__ = ["StripeUtil"]
25class StripeUtil:
26 __slots__ = ()
28 @staticmethod
29 def cvt_account_type(account_type: str | None) -> StripeAccountType | None:
30 if account_type is None:
31 return None
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
40 @staticmethod
41 def cvt_stripe_currency(
42 currency: StripeCurrencyCode | str | None,
43 ) -> StripeCurrencyCode | None:
44 if currency is None:
45 return None
47 if isinstance(currency, StripeCurrencyCode):
48 return currency
50 try:
51 return StripeCurrencyCode(currency.upper())
52 except ValueError:
53 msg = f"Invalid currency code {currency}.."
54 LOG().warning(msg)
55 return None
57 @staticmethod
58 def cvt_stripe_country(country: StripeCountryCode | str | None) -> StripeCountryCode | None:
59 if country is None:
60 return None
62 if isinstance(country, StripeCountryCode):
63 return country
65 try:
66 return StripeCountryCode(country.upper())
67 except ValueError:
68 msg = f"Invalid country code {country}.."
69 LOG().warning(msg)
70 return None
72 @staticmethod
73 def get_first_last_name(name: str) -> tuple[str, str] | None:
74 if not is_text_present(name):
75 return None
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
83 return None
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.
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()
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()
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()
119 # Filter to alphabetic chars only
120 alpha_chars = "".join(c for c in local if c.isalpha())
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()
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 )