parent
17f59c1fcb
commit
f65fac9661
@ -0,0 +1,18 @@ |
||||
# Generated by Django 5.1 on 2025-06-23 16:47 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tournaments', '0128_club_data_access_ids_court_data_access_ids_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='tournament', |
||||
name='currency_code', |
||||
field=models.CharField(blank=True, default='EUR', max_length=3, null=True), |
||||
), |
||||
] |
||||
@ -0,0 +1,189 @@ |
||||
""" |
||||
Currency service for handling multi-currency support in tournaments. |
||||
""" |
||||
|
||||
class CurrencyService: |
||||
"""Service for handling currency formatting, symbols, and conversions.""" |
||||
|
||||
# Currency symbols mapping |
||||
CURRENCY_SYMBOLS = { |
||||
'EUR': '€', |
||||
'USD': '$', |
||||
'GBP': '£', |
||||
'CHF': 'CHF', |
||||
'CAD': 'C$', |
||||
'AUD': 'A$', |
||||
'JPY': '¥', |
||||
'CNY': '¥', |
||||
'SEK': 'kr', |
||||
'NOK': 'kr', |
||||
'DKK': 'kr', |
||||
'PLN': 'zł', |
||||
'CZK': 'Kč', |
||||
'HUF': 'Ft', |
||||
'RON': 'lei', |
||||
'BGN': 'лв', |
||||
'HRK': 'kn', |
||||
'RSD': 'RSD', |
||||
'BAM': 'KM', |
||||
'MKD': 'ден', |
||||
'ALL': 'L', |
||||
'MDL': 'L', |
||||
'UAH': '₴', |
||||
'RUB': '₽', |
||||
'BYN': 'Br', |
||||
'LTL': 'Lt', |
||||
'LVL': 'Ls', |
||||
'EEK': 'kr', |
||||
'ISK': 'kr', |
||||
'TRY': '₺', |
||||
'ILS': '₪', |
||||
'AED': 'د.إ', |
||||
'SAR': 'ر.س', |
||||
'QAR': 'ر.ق', |
||||
'KWD': 'د.ك', |
||||
'BHD': '.د.ب', |
||||
'OMR': 'ر.ع.', |
||||
'JOD': 'د.أ', |
||||
'LBP': 'ل.ل', |
||||
'EGP': 'ج.م', |
||||
'MAD': 'د.م.', |
||||
'TND': 'د.ت', |
||||
'DZD': 'د.ج', |
||||
'LYD': 'ل.د', |
||||
'ZAR': 'R', |
||||
'NGN': '₦', |
||||
'GHS': '₵', |
||||
'KES': 'KSh', |
||||
'UGX': 'USh', |
||||
'TZS': 'TSh', |
||||
'ETB': 'Br', |
||||
'XOF': 'CFA', |
||||
'XAF': 'FCFA', |
||||
'MZN': 'MT', |
||||
'BWP': 'P', |
||||
'ZMW': 'ZK', |
||||
'INR': '₹', |
||||
'PKR': '₨', |
||||
'BDT': '৳', |
||||
'LKR': '₨', |
||||
'MVR': 'Rf', |
||||
'NPR': '₨', |
||||
'BTN': 'Nu.', |
||||
'THB': '฿', |
||||
'MYR': 'RM', |
||||
'SGD': 'S$', |
||||
'IDR': 'Rp', |
||||
'PHP': '₱', |
||||
'VND': '₫', |
||||
'KHR': '៛', |
||||
'LAK': '₭', |
||||
'MMK': 'K', |
||||
'KRW': '₩', |
||||
'TWD': 'NT$', |
||||
'HKD': 'HK$', |
||||
'MOP': 'MOP$', |
||||
'BND': 'B$', |
||||
'FJD': 'FJ$', |
||||
'PGK': 'K', |
||||
'WST': 'WS$', |
||||
'TOP': 'T$', |
||||
'VUV': 'VT', |
||||
'SBD': 'SI$', |
||||
'MXN': '$', |
||||
'GTQ': 'Q', |
||||
'BZD': 'BZ$', |
||||
'SVC': '$', |
||||
'HNL': 'L', |
||||
'NIO': 'C$', |
||||
'CRC': '₡', |
||||
'PAB': 'B/.', |
||||
'COP': '$', |
||||
'VES': 'Bs.S', |
||||
'GYD': 'GY$', |
||||
'SRD': 'Sr$', |
||||
'UYU': '$U', |
||||
'PYG': '₲', |
||||
'BOB': 'Bs.', |
||||
'BRL': 'R$', |
||||
'PEN': 'S/', |
||||
'ECU': '$', |
||||
'CLP': '$', |
||||
'ARS': '$', |
||||
'FKP': '£', |
||||
'XPF': '₣' |
||||
} |
||||
|
||||
# Zero-decimal currencies (amounts are in their base unit, not subdivided) |
||||
ZERO_DECIMAL_CURRENCIES = { |
||||
'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', |
||||
'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF' |
||||
} |
||||
|
||||
@classmethod |
||||
def get_currency_symbol(cls, currency_code): |
||||
"""Get the currency symbol for a given currency code.""" |
||||
if not currency_code: |
||||
return '€' # Default to Euro symbol |
||||
return cls.CURRENCY_SYMBOLS.get(currency_code.upper(), currency_code.upper()) |
||||
|
||||
@classmethod |
||||
def format_amount(cls, amount, currency_code, show_symbol=True): |
||||
"""Format amount with proper currency symbol and decimal places.""" |
||||
if amount is None: |
||||
return "0" |
||||
|
||||
currency_code = currency_code.upper() if currency_code else 'EUR' |
||||
|
||||
# Handle zero-decimal currencies |
||||
if currency_code in cls.ZERO_DECIMAL_CURRENCIES: |
||||
formatted_amount = f"{int(amount)}" |
||||
else: |
||||
# Format with 2 decimal places, removing unnecessary zeros |
||||
if amount == int(amount): |
||||
formatted_amount = f"{int(amount)}" |
||||
else: |
||||
formatted_amount = f"{amount:.2f}" |
||||
|
||||
if show_symbol: |
||||
symbol = cls.get_currency_symbol(currency_code) |
||||
return f"{formatted_amount} {symbol}" |
||||
|
||||
return formatted_amount |
||||
|
||||
@classmethod |
||||
def convert_to_stripe_amount(cls, amount, currency_code): |
||||
"""Convert amount to Stripe's expected format (minor units).""" |
||||
if not amount: |
||||
return 0 |
||||
|
||||
currency_code = currency_code.upper() if currency_code else 'EUR' |
||||
|
||||
# Zero-decimal currencies don't need conversion |
||||
if currency_code in cls.ZERO_DECIMAL_CURRENCIES: |
||||
return int(amount) |
||||
|
||||
# Two-decimal currencies need to be multiplied by 100 |
||||
return int(amount * 100) |
||||
|
||||
@classmethod |
||||
def convert_from_stripe_amount(cls, stripe_amount, currency_code): |
||||
"""Convert Stripe amount (minor units) back to standard decimal format.""" |
||||
if not stripe_amount: |
||||
return 0.0 |
||||
|
||||
currency_code = currency_code.upper() if currency_code else 'EUR' |
||||
|
||||
# Zero-decimal currencies are already in the correct format |
||||
if currency_code in cls.ZERO_DECIMAL_CURRENCIES: |
||||
return float(stripe_amount) |
||||
|
||||
# Two-decimal currencies need to be divided by 100 |
||||
return float(stripe_amount) / 100 |
||||
|
||||
@classmethod |
||||
def validate_currency_code(cls, currency_code): |
||||
"""Validate if a currency code is supported.""" |
||||
if not currency_code: |
||||
return False |
||||
return currency_code.upper() in cls.CURRENCY_SYMBOLS |
||||
Loading…
Reference in new issue