btcpayserver/BTCPayServer/Services/DisplayFormatter.cs
Nicolas Dorier b4946f4db1
Fix divisibility in invoice details of lightning amounts (#6202)
* Fix divisibility in invoice details of lightning amounts

This PR will show 11 decimal in the invoice details for BTC amount
of lightning payment methods.

It also hacks around the fact that some
lightning clients don't create the requested amount of sats, which
resulted in over or under payments. (Blink not supporting msats, and
strike)

Now, In that case, a payment method fee (which can be negative) called tweak fee
will be added to the prompt.

We are also hiding this tweak fee from the user in the checkout page in
order to not disturb the UI with inconsequential fee of 0.000000001 sats.

* Only show 8 digits in checkout, even if amount is 11 digits
2024-09-12 12:43:08 +09:00

75 lines
2.7 KiB
C#

using System;
using System.Globalization;
using BTCPayServer.Rating;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Reporting;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services;
public class DisplayFormatter
{
private readonly CurrencyNameTable _currencyNameTable;
public DisplayFormatter(CurrencyNameTable currencyNameTable)
{
_currencyNameTable = currencyNameTable;
}
public enum CurrencyFormat
{
Code,
Symbol,
CodeAndSymbol,
None
}
/// <summary>
/// Format a currency, rounded to significant divisibility
/// </summary>
/// <param name="value">The value</param>
/// <param name="currency">Currency code</param>
/// <param name="format">The format, defaults to amount + code, e.g. 1.234,56 USD</param>
/// <returns>Formatted amount and currency string</returns>
public string Currency(decimal value, string currency, CurrencyFormat format = CurrencyFormat.Code, int? divisibility = null)
{
var provider = _currencyNameTable.GetNumberFormatInfo(currency, true);
var currencyData = _currencyNameTable.GetCurrencyData(currency, true);
var div = divisibility is int d ? d : currencyData.Divisibility;
value = value.RoundToSignificant(ref div);
if (divisibility != provider.CurrencyDecimalDigits)
{
provider = (NumberFormatInfo)provider.Clone();
provider.CurrencyDecimalDigits = div;
}
var formatted = value.ToString("C", provider);
// Ensure we are not using the symbol for BTC — we made that design choice consciously.
if (format == CurrencyFormat.Symbol && currencyData.Code == "BTC")
{
format = CurrencyFormat.Code;
}
return format switch
{
CurrencyFormat.None => formatted.Replace(provider.CurrencySymbol, "").Trim(),
CurrencyFormat.Code => $"{formatted.Replace(provider.CurrencySymbol, "").Trim()} {currency}",
CurrencyFormat.Symbol => formatted,
CurrencyFormat.CodeAndSymbol => $"{formatted} ({currency})",
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
};
}
public string Currency(string value, string currency, CurrencyFormat format = CurrencyFormat.Code)
{
return Currency(decimal.Parse(value, CultureInfo.InvariantCulture), currency, format);
}
public JObject ToFormattedAmount(decimal value, string currency)
{
var currencyData = _currencyNameTable.GetCurrencyData(currency, true);
var divisibility = currencyData.Divisibility;
return new FormattedAmount(value, divisibility).ToJObject();
}
}