Files
payload-billing/src/providers/currency.ts
Bas van den Aakster bf9940924c security: Address critical security vulnerabilities and improve code quality
🔒 Security Fixes:
- Make webhook signature validation required for production
- Prevent information disclosure by returning 200 for all webhook responses
- Sanitize external error messages while preserving internal logging

🔧 Code Quality Improvements:
- Add URL validation to prevent localhost usage in production
- Create currency utilities for proper handling of non-centesimal currencies
- Replace unsafe 'any' types with type-safe ProviderData wrapper
- Add comprehensive input validation for amounts, currencies, and descriptions
- Set default Stripe API version for consistency

📦 New Features:
- Currency conversion utilities supporting JPY, KRW, and other special cases
- Type-safe provider data structure with metadata
- Enhanced validation functions for payment data

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-17 18:38:44 +02:00

94 lines
2.5 KiB
TypeScript

/**
* Currency utilities for payment processing
*/
// Currencies that don't use centesimal units (no decimal places)
const NON_CENTESIMAL_CURRENCIES = new Set([
'BIF', // Burundian Franc
'CLP', // Chilean Peso
'DJF', // Djiboutian Franc
'GNF', // Guinean Franc
'JPY', // Japanese Yen
'KMF', // Comorian Franc
'KRW', // South Korean Won
'MGA', // Malagasy Ariary
'PYG', // Paraguayan Guaraní
'RWF', // Rwandan Franc
'UGX', // Ugandan Shilling
'VND', // Vietnamese Đồng
'VUV', // Vanuatu Vatu
'XAF', // Central African CFA Franc
'XOF', // West African CFA Franc
'XPF', // CFP Franc
])
// Currencies that use 3 decimal places
const THREE_DECIMAL_CURRENCIES = new Set([
'BHD', // Bahraini Dinar
'IQD', // Iraqi Dinar
'JOD', // Jordanian Dinar
'KWD', // Kuwaiti Dinar
'LYD', // Libyan Dinar
'OMR', // Omani Rial
'TND', // Tunisian Dinar
])
/**
* Convert amount from smallest unit to decimal for display
* @param amount - Amount in smallest unit (e.g., cents for USD)
* @param currency - ISO 4217 currency code
* @returns Formatted amount string for the payment provider
*/
export function formatAmountForProvider(amount: number, currency: string): string {
const upperCurrency = currency.toUpperCase()
if (NON_CENTESIMAL_CURRENCIES.has(upperCurrency)) {
// No decimal places
return amount.toString()
}
if (THREE_DECIMAL_CURRENCIES.has(upperCurrency)) {
// 3 decimal places
return (amount / 1000).toFixed(3)
}
// Default: 2 decimal places (most currencies)
return (amount / 100).toFixed(2)
}
/**
* Get the number of decimal places for a currency
* @param currency - ISO 4217 currency code
* @returns Number of decimal places
*/
export function getCurrencyDecimals(currency: string): number {
const upperCurrency = currency.toUpperCase()
if (NON_CENTESIMAL_CURRENCIES.has(upperCurrency)) {
return 0
}
if (THREE_DECIMAL_CURRENCIES.has(upperCurrency)) {
return 3
}
return 2
}
/**
* Validate currency code format
* @param currency - Currency code to validate
* @returns True if valid ISO 4217 format
*/
export function isValidCurrencyCode(currency: string): boolean {
return /^[A-Z]{3}$/.test(currency.toUpperCase())
}
/**
* Validate amount is positive and within reasonable limits
* @param amount - Amount to validate
* @returns True if valid
*/
export function isValidAmount(amount: number): boolean {
return Number.isInteger(amount) && amount > 0 && amount <= 99999999999 // Max ~999 million in major units
}