2020-06-28 21:44:35 -05:00
using System ;
2017-09-13 15:47:34 +09:00
using System.Collections.Generic ;
2020-06-28 17:55:27 +09:00
using System.ComponentModel.DataAnnotations.Schema ;
2023-10-18 19:07:30 +09:00
using System.Globalization ;
2020-06-28 17:55:27 +09:00
using System.Linq ;
2024-04-04 16:31:04 +09:00
using System.Text ;
2022-03-02 18:05:16 +01:00
using BTCPayServer.Abstractions.Extensions ;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Client.Models ;
using BTCPayServer.Data ;
using BTCPayServer.JsonConverters ;
2017-09-26 01:31:43 +09:00
using BTCPayServer.Models ;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Payments ;
using BTCPayServer.Payments.Bitcoin ;
2023-01-13 09:29:41 +01:00
using BTCPayServer.Payments.Lightning ;
2024-04-04 16:31:04 +09:00
using BTCPayServer.Rating ;
using Microsoft.AspNetCore.Mvc ;
2020-06-28 17:55:27 +09:00
using NBitcoin ;
2017-09-26 01:31:43 +09:00
using NBitcoin.DataEncoders ;
2020-06-28 17:55:27 +09:00
using NBitpayClient ;
2017-12-21 15:52:04 +09:00
using NBXplorer ;
2020-06-28 17:55:27 +09:00
using Newtonsoft.Json ;
2021-08-03 17:03:00 +09:00
using Newtonsoft.Json.Converters ;
2020-06-28 17:55:27 +09:00
using Newtonsoft.Json.Linq ;
2020-08-25 14:33:00 +09:00
using Newtonsoft.Json.Serialization ;
2024-04-04 16:31:04 +09:00
using static BTCPayServer . Controllers . BitpayRateController ;
2017-09-13 15:47:34 +09:00
2017-10-20 14:06:37 -05:00
namespace BTCPayServer.Services.Invoices
2017-09-13 15:47:34 +09:00
{
2021-10-25 08:18:02 +02:00
public class InvoiceCryptoInfo : NBitpayClient . InvoiceCryptoInfo
{
[JsonProperty("paymentUrls")]
public new InvoicePaymentUrls PaymentUrls { get ; set ; }
public class InvoicePaymentUrls : NBitpayClient . InvoicePaymentUrls
{
[JsonExtensionData] public Dictionary < string , JToken > AdditionalData { get ; set ; }
}
}
2023-02-25 23:34:49 +09:00
public class InvoiceMetadata : IHasAdditionalData
2017-10-27 17:53:04 +09:00
{
2020-08-25 14:33:00 +09:00
public static readonly JsonSerializer MetadataSerializer ;
static InvoiceMetadata ( )
{
var seria = new JsonSerializer ( ) ;
seria . DefaultValueHandling = DefaultValueHandling . Ignore ;
seria . FloatParseHandling = FloatParseHandling . Decimal ;
seria . ContractResolver = new CamelCasePropertyNamesContractResolver ( ) ;
MetadataSerializer = seria ;
}
2021-12-31 16:59:02 +09:00
2021-04-28 09:49:10 +02:00
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string OrderId
2021-04-28 09:49:10 +02:00
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "orderId" ) ;
set = > this . SetAdditionalData ( "orderId" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-07-14 13:43:13 +02:00
public string OrderUrl
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "orderUrl" ) ;
set = > this . SetAdditionalData ( "orderUrl" , value ) ;
2021-07-14 13:43:13 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string PaymentRequestId
2021-07-14 13:43:13 +02:00
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "paymentRequestId" ) ;
set = > this . SetAdditionalData ( "paymentRequestId" , value ) ;
2021-07-14 13:43:13 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string BuyerName
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "buyerName" ) ;
set = > this . SetAdditionalData ( "buyerName" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string BuyerEmail
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "buyerEmail" ) ;
set = > this . SetAdditionalData ( "buyerEmail" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string BuyerCountry
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "buyerCountry" ) ;
set = > this . SetAdditionalData ( "buyerCountry" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string BuyerZip
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "buyerZip" ) ;
set = > this . SetAdditionalData ( "buyerZip" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string BuyerState
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "buyerState" ) ;
set = > this . SetAdditionalData ( "buyerState" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string BuyerCity
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "buyerCity" ) ;
set = > this . SetAdditionalData ( "buyerCity" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string BuyerAddress2
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "buyerAddress2" ) ;
set = > this . SetAdditionalData ( "buyerAddress2" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string BuyerAddress1
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "buyerAddress1" ) ;
set = > this . SetAdditionalData ( "buyerAddress1" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string BuyerPhone
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "buyerPhone" ) ;
set = > this . SetAdditionalData ( "buyerPhone" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string ItemDesc
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "itemDesc" ) ;
set = > this . SetAdditionalData ( "itemDesc" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public string ItemCode
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < string > ( "itemCode" ) ;
set = > this . SetAdditionalData ( "itemCode" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public bool? Physical
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < bool? > ( "physical" ) ;
set = > this . SetAdditionalData ( "physical" , value ) ;
2021-04-28 09:49:10 +02:00
}
[JsonIgnore]
2021-12-31 16:59:02 +09:00
public decimal? TaxIncluded
{
2023-02-25 23:34:49 +09:00
get = > this . GetAdditionalData < decimal? > ( "taxIncluded" ) ;
set = > this . SetAdditionalData ( "taxIncluded" , value ) ;
2021-04-28 09:49:10 +02:00
}
2023-02-25 23:34:49 +09:00
/// <summary>
/// posData is a field that may be treated differently for presentation and in some legacy API
/// Before, it was a string field which could contain some JSON data inside.
/// For making it easier to query on the DB, and for logic using PosData in the code, we decided to
/// parse it as a JObject.
///
/// This property will return the posData as a JObject, even if it's a Json string inside.
/// </summary>
[JsonIgnore]
public JObject PosData
2021-04-28 09:49:10 +02:00
{
2023-02-25 23:34:49 +09:00
get
2021-04-08 09:42:18 +02:00
{
2023-02-25 23:34:49 +09:00
if ( AdditionalData = = null | | ! ( AdditionalData . TryGetValue ( "posData" , out var jt ) is true ) )
return default ;
if ( jt . Type = = JTokenType . Null )
return default ;
if ( jt . Type = = JTokenType . String )
try
{
return JObject . Parse ( jt . Value < string > ( ) ) ;
}
catch
{
return null ;
}
if ( jt . Type = = JTokenType . Object )
return ( JObject ) jt ;
return null ;
2021-04-08 09:42:18 +02:00
}
2021-04-28 09:49:10 +02:00
2023-02-25 23:34:49 +09:00
set
2021-04-28 09:49:10 +02:00
{
2023-02-25 23:34:49 +09:00
this . SetAdditionalData < JObject > ( "posData" , value ) ;
2021-04-28 09:49:10 +02:00
}
}
2023-02-25 23:34:49 +09:00
/// <summary>
/// See comments on <see cref="PosData"/>
/// </summary>
[JsonIgnore]
public string PosDataLegacy
2021-04-28 09:49:10 +02:00
{
2023-02-25 23:34:49 +09:00
get
2021-04-28 09:49:10 +02:00
{
2023-02-25 23:34:49 +09:00
return this . GetAdditionalData < string > ( "posData" ) ;
2021-04-28 09:49:10 +02:00
}
2023-02-25 23:34:49 +09:00
set
2021-04-28 09:49:10 +02:00
{
2023-02-25 23:34:49 +09:00
if ( value ! = null )
2021-04-08 09:42:18 +02:00
{
2023-02-25 23:34:49 +09:00
try
2021-04-08 09:42:18 +02:00
{
2023-02-25 23:34:49 +09:00
PosData = JObject . Parse ( value ) ;
return ;
2021-04-08 09:42:18 +02:00
}
2023-02-25 23:34:49 +09:00
catch
2021-04-08 09:42:18 +02:00
{
}
}
2023-02-25 23:34:49 +09:00
this . SetAdditionalData < string > ( "posData" , value ) ;
2021-04-08 09:42:18 +02:00
}
}
2023-02-25 23:34:49 +09:00
[JsonExtensionData]
public IDictionary < string , JToken > AdditionalData { get ; set ; }
2021-12-31 16:59:02 +09:00
2020-08-25 14:33:00 +09:00
public static InvoiceMetadata FromJObject ( JObject jObject )
2019-01-24 20:53:29 +09:00
{
2020-08-25 14:33:00 +09:00
return jObject . ToObject < InvoiceMetadata > ( MetadataSerializer ) ;
2019-01-24 20:53:29 +09:00
}
2020-08-25 14:33:00 +09:00
public JObject ToJObject ( )
2017-10-27 17:53:04 +09:00
{
2020-08-25 14:33:00 +09:00
return JObject . FromObject ( this , MetadataSerializer ) ;
2017-10-27 17:53:04 +09:00
}
}
2020-08-25 14:33:00 +09:00
2023-02-25 23:34:49 +09:00
public class InvoiceEntity : IHasAdditionalData
2017-10-27 17:53:04 +09:00
{
2020-08-25 14:33:00 +09:00
class BuyerInformation
{
[JsonProperty(PropertyName = "buyerName")]
2020-11-08 23:37:45 -06:00
public string BuyerName { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "buyerEmail")]
2020-11-08 23:37:45 -06:00
public string BuyerEmail { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "buyerCountry")]
2020-11-08 23:37:45 -06:00
public string BuyerCountry { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "buyerZip")]
2020-11-08 23:37:45 -06:00
public string BuyerZip { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "buyerState")]
2020-11-08 23:37:45 -06:00
public string BuyerState { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "buyerCity")]
2020-11-08 23:37:45 -06:00
public string BuyerCity { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "buyerAddress2")]
2020-11-08 23:37:45 -06:00
public string BuyerAddress2 { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "buyerAddress1")]
2020-11-08 23:37:45 -06:00
public string BuyerAddress1 { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "buyerPhone")]
2020-11-08 23:37:45 -06:00
public string BuyerPhone { get ; set ; }
2020-08-25 14:33:00 +09:00
}
2020-11-08 23:37:45 -06:00
2020-08-25 14:33:00 +09:00
class ProductInformation
{
[JsonProperty(PropertyName = "itemDesc")]
2020-11-08 23:37:45 -06:00
public string ItemDesc { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "itemCode")]
2020-11-08 23:37:45 -06:00
public string ItemCode { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "physical")]
2020-11-08 23:37:45 -06:00
public bool Physical { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "price")]
2020-11-08 23:37:45 -06:00
public decimal Price { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)]
2020-11-08 23:37:45 -06:00
public decimal TaxIncluded { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonProperty(PropertyName = "currency")]
2020-11-08 23:37:45 -06:00
public string Currency { get ; set ; }
2020-08-25 14:33:00 +09:00
}
2019-02-19 12:48:48 +09:00
public const int InternalTagSupport_Version = 1 ;
2020-08-25 14:33:00 +09:00
public const int GreenfieldInvoices_Version = 2 ;
2024-04-04 16:31:04 +09:00
public const int LeanInvoices_Version = 3 ;
public const int Lastest_Version = 3 ;
public int Version { get ; set ; }
[JsonIgnore]
2020-11-08 23:37:45 -06:00
public string Id { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonIgnore]
2020-11-08 23:37:45 -06:00
public string StoreId { get ; set ; }
2017-09-14 01:06:11 +09:00
2020-11-08 23:37:45 -06:00
public SpeedPolicy SpeedPolicy { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonProperty]
2020-12-10 23:34:50 +09:00
public string DefaultLanguage { get ; set ; }
2024-04-04 22:50:12 +09:00
[JsonIgnore]
2020-11-08 23:37:45 -06:00
public DateTimeOffset InvoiceTime { get ; set ; }
public DateTimeOffset ExpirationTime { get ; set ; }
public InvoiceMetadata Metadata { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonIgnore]
2020-08-25 14:33:00 +09:00
public decimal Price { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonIgnore]
2020-08-25 14:33:00 +09:00
public string Currency { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonConverter(typeof(PaymentMethodIdJsonConverter))]
public PaymentMethodId DefaultPaymentMethod { get ; set ; }
2020-08-25 14:33:00 +09:00
[JsonExtensionData]
public IDictionary < string , JToken > AdditionalData { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonProperty]
2019-02-19 11:14:21 +09:00
public HashSet < string > InternalTags { get ; set ; } = new HashSet < string > ( ) ;
2019-02-25 16:15:45 +09:00
2020-09-21 10:24:56 +02:00
public string [ ] GetInternalTags ( string prefix )
2019-02-25 16:15:45 +09:00
{
return InternalTags = = null ? Array . Empty < string > ( ) : InternalTags
2020-09-21 10:24:56 +02:00
. Where ( t = > t . StartsWith ( prefix , StringComparison . InvariantCulture ) )
. Select ( t = > t . Substring ( prefix . Length ) ) . ToArray ( ) ;
2019-02-25 16:15:45 +09:00
}
2020-06-28 17:55:27 +09:00
2024-04-04 16:31:04 +09:00
public decimal GetInvoiceRate ( string currency )
2023-02-25 23:34:49 +09:00
{
2024-04-04 16:31:04 +09:00
ArgumentNullException . ThrowIfNull ( currency ) ;
if ( Currency is null )
throw new InvalidOperationException ( "The Currency of the invoice isn't set" ) ;
return GetRate ( new CurrencyPair ( currency , Currency ) ) ;
2023-02-25 23:34:49 +09:00
}
2024-04-04 16:31:04 +09:00
public RateRules GetRateRules ( )
2018-02-20 14:23:50 +09:00
{
2024-04-04 16:31:04 +09:00
StringBuilder builder = new StringBuilder ( ) ;
#pragma warning disable CS0618 // Type or member is obsolete
foreach ( var r in Rates )
{
if ( r . Key . Contains ( '_' , StringComparison . Ordinal ) )
builder . AppendLine ( $"{r.Key} = {r.Value.ToString(CultureInfo.InvariantCulture)};" ) ;
else
builder . AppendLine ( $"{r.Key}_{Currency} = {r.Value.ToString(CultureInfo.InvariantCulture)};" ) ;
}
#pragma warning restore CS0618 // Type or member is obsolete
if ( RateRules . TryParse ( builder . ToString ( ) , out var rules ) )
return rules ;
throw new FormatException ( "Invalid rate rules" ) ;
2018-02-20 14:23:50 +09:00
}
2024-04-04 16:31:04 +09:00
public bool TryGetRate ( string currency , out decimal rate )
2018-02-20 14:23:50 +09:00
{
2024-04-04 16:31:04 +09:00
return TryGetRate ( new CurrencyPair ( Currency , currency ) , out rate ) ;
2018-02-26 00:48:12 +09:00
}
2024-04-04 16:31:04 +09:00
public bool TryGetRate ( CurrencyPair pair , out decimal rate )
2018-01-06 18:57:56 +09:00
{
2024-04-04 16:31:04 +09:00
#pragma warning disable CS0618 // Type or member is obsolete
if ( pair . Right = = Currency & & Rates . TryGetValue ( pair . Left , out rate ) ) // Fast lane
return true ;
#pragma warning restore CS0618 // Type or member is obsolete
var rule = GetRateRules ( ) . GetRuleFor ( pair ) ;
rule . Reevaluate ( ) ;
if ( rule . BidAsk is null )
2018-01-06 18:57:56 +09:00
{
2024-04-04 16:31:04 +09:00
rate = 0.0 m ;
return false ;
2018-01-06 18:57:56 +09:00
}
2024-04-04 16:31:04 +09:00
rate = rule . BidAsk . Bid ;
return true ;
2018-01-06 18:57:56 +09:00
}
2024-04-04 16:31:04 +09:00
public decimal GetRate ( CurrencyPair pair )
2018-01-06 18:57:56 +09:00
{
2024-04-04 16:31:04 +09:00
ArgumentNullException . ThrowIfNull ( pair ) ;
#pragma warning disable CS0618 // Type or member is obsolete
if ( pair . Right = = Currency & & Rates . TryGetValue ( pair . Left , out var rate ) ) // Fast lane
return rate ;
#pragma warning restore CS0618 // Type or member is obsolete
var rule = GetRateRules ( ) . GetRuleFor ( pair ) ;
rule . Reevaluate ( ) ;
if ( rule . BidAsk is null )
throw new InvalidOperationException ( $"Rate rule is not evaluated ({rule.Errors.First()})" ) ;
return rule . BidAsk . Bid ;
2018-01-06 18:57:56 +09:00
}
2024-04-04 16:31:04 +09:00
public void AddRate ( CurrencyPair pair , decimal rate )
{
#pragma warning disable CS0618 // Type or member is obsolete
var v = pair . Right = = Currency ? pair . Left : pair . ToString ( ) ;
Rates . Add ( v , rate ) ;
#pragma warning restore CS0618 // Type or member is obsolete
}
[Obsolete("Use GetRate instead")]
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
2023-07-19 18:47:32 +09:00
public Dictionary < string , decimal > Rates
{
get ;
2024-04-04 16:31:04 +09:00
set ;
} = new Dictionary < string , decimal > ( ) ;
2023-07-19 18:47:32 +09:00
public void UpdateTotals ( )
{
PaidAmount = new Amounts ( )
{
Currency = Currency
} ;
foreach ( var payment in GetPayments ( false ) )
{
2024-04-04 16:31:04 +09:00
payment . Rate = GetInvoiceRate ( payment . Currency ) ;
2023-07-19 18:47:32 +09:00
payment . InvoiceEntity = this ;
payment . UpdateAmounts ( ) ;
if ( payment . Accounted )
{
PaidAmount . Gross + = payment . InvoicePaidAmount . Gross ;
PaidAmount . Net + = payment . InvoicePaidAmount . Net ;
}
}
NetDue = Price - PaidAmount . Net ;
MinimumNetDue = Price * ( 1.0 m - ( ( decimal ) PaymentTolerance / 100.0 m ) ) - PaidAmount . Net ;
PaidFee = PaidAmount . Gross - PaidAmount . Net ;
if ( NetDue < 0.0 m )
{
// If any payment method exactly pay the invoice, the overpayment is caused by
// rounding limitation of the underlying payment method.
// Document this overpayment as dust, and set the net due to 0
2024-04-04 16:31:04 +09:00
if ( GetPaymentPrompts ( ) . Any ( p = > p . Calculate ( ) . DueUncapped = = 0.0 m ) )
2023-07-19 18:47:32 +09:00
{
Dust = - NetDue ;
NetDue = 0.0 m ;
}
}
}
/// <summary>
/// Overpaid amount caused by payment method
/// Example: If you need to pay 124.4 sats, the on-chain payment need to be technically rounded to 125 sats, the extra 0.6 sats shouldn't be considered an over payment.
/// </summary>
[JsonIgnore]
public decimal Dust { get ; set ; }
/// <summary>
/// The due to consider the invoice paid (can be negative if over payment)
/// </summary>
[JsonIgnore]
public decimal NetDue
{
get ;
set ;
}
/// <summary>
2023-08-07 16:26:37 +09:00
/// Minimum due to consider the invoice paid (can be negative if overpaid)
2023-07-19 18:47:32 +09:00
/// </summary>
[JsonIgnore]
public decimal MinimumNetDue { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonIgnore]
2023-07-19 18:47:32 +09:00
public bool IsUnderPaid = > MinimumNetDue > 0 ;
[JsonIgnore]
public bool IsOverPaid = > NetDue < 0 ;
/// <summary>
/// Total of network fee paid by accounted payments
/// </summary>
[JsonIgnore]
public decimal PaidFee { get ; set ; }
2018-12-10 21:48:28 +09:00
[JsonIgnore]
2024-05-15 07:49:53 +09:00
public InvoiceStatus Status { get ; set ; }
2018-12-10 21:48:28 +09:00
[JsonIgnore]
2020-11-08 23:37:45 -06:00
public InvoiceExceptionStatus ExceptionStatus { get ; set ; }
2018-01-10 18:30:45 +09:00
[Obsolete("Use GetPayments instead")]
2024-04-04 16:31:04 +09:00
[JsonIgnore]
2020-11-08 23:37:45 -06:00
public List < PaymentEntity > Payments { get ; set ; }
2018-01-10 18:30:45 +09:00
#pragma warning disable CS0618
2021-05-14 16:16:19 +09:00
public List < PaymentEntity > GetPayments ( bool accountedOnly )
2018-01-10 18:30:45 +09:00
{
2024-04-04 16:31:04 +09:00
return Payments ? . Where ( entity = > ( ! accountedOnly | | entity . Accounted ) ) . ToList ( ) ? ? new List < PaymentEntity > ( ) ;
2018-01-10 18:30:45 +09:00
}
2024-04-04 16:31:04 +09:00
public List < PaymentEntity > GetPayments ( string currency , bool accountedOnly )
2018-01-10 18:30:45 +09:00
{
2024-04-04 16:31:04 +09:00
return GetPayments ( accountedOnly ) . Where ( p = > p . Currency = = currency ) . ToList ( ) ;
2018-01-10 18:30:45 +09:00
}
#pragma warning restore CS0618
2023-05-11 10:38:40 +02:00
2024-04-04 16:31:04 +09:00
[JsonProperty]
2023-05-11 10:38:40 +02:00
public string StoreSupportUrl { get ; set ; }
2019-09-02 16:04:41 +09:00
[JsonProperty("redirectURL")]
2020-11-08 23:37:45 -06:00
public string RedirectURLTemplate { get ; set ; }
2019-09-02 16:04:41 +09:00
[JsonIgnore]
2019-09-04 18:20:36 +09:00
public Uri RedirectURL = > FillPlaceholdersUri ( RedirectURLTemplate ) ;
2019-09-02 16:04:41 +09:00
2019-09-04 18:20:36 +09:00
private Uri FillPlaceholdersUri ( string v )
2019-09-02 16:04:41 +09:00
{
2020-08-25 14:33:00 +09:00
var uriStr = ( v ? ? string . Empty ) . Replace ( "{OrderId}" , System . Web . HttpUtility . UrlEncode ( Metadata . OrderId ) ? ? "" , StringComparison . OrdinalIgnoreCase )
2020-08-04 14:05:36 +09:00
. Replace ( "{InvoiceId}" , System . Web . HttpUtility . UrlEncode ( Id ) ? ? "" , StringComparison . OrdinalIgnoreCase ) ;
2019-09-04 18:01:26 +09:00
if ( Uri . TryCreate ( uriStr , UriKind . Absolute , out var uri ) & & ( uri . Scheme = = "http" | | uri . Scheme = = "https" ) )
2019-09-04 18:20:36 +09:00
return uri ;
2019-09-04 18:01:26 +09:00
return null ;
2019-09-02 16:04:41 +09:00
}
2024-04-04 16:31:04 +09:00
[JsonProperty]
2020-11-08 23:37:45 -06:00
public bool RedirectAutomatically { get ; set ; }
public bool FullNotifications { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonProperty]
2020-11-08 23:37:45 -06:00
public string NotificationEmail { get ; set ; }
2019-09-02 16:04:41 +09:00
[JsonProperty("notificationURL")]
2020-11-08 23:37:45 -06:00
public string NotificationURLTemplate { get ; set ; }
2019-09-02 16:04:41 +09:00
[JsonIgnore]
2019-09-04 18:20:36 +09:00
public Uri NotificationURL = > FillPlaceholdersUri ( NotificationURLTemplate ) ;
2020-11-08 23:37:45 -06:00
public string ServerUrl { get ; set ; }
2017-12-21 15:52:04 +09:00
2024-04-04 16:31:04 +09:00
[Obsolete("Use Set/GetPaymentPrompts() instead")]
[JsonProperty(PropertyName = "prompts")]
public JObject PaymentPrompts { get ; set ; }
2017-12-21 15:52:04 +09:00
2024-04-04 16:31:04 +09:00
[JsonProperty]
2020-11-08 23:37:45 -06:00
public DateTimeOffset MonitoringExpiration { get ; set ; }
2017-09-13 15:47:34 +09:00
2024-04-04 16:31:04 +09:00
[JsonIgnore]
2020-11-08 23:37:45 -06:00
public HashSet < string > AvailableAddressHashes { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonProperty]
2018-01-08 04:14:35 +09:00
public bool ExtendedNotifications { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonProperty]
2018-05-04 16:15:34 +02:00
public double PaymentTolerance { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonIgnore]
2020-05-07 12:50:07 +02:00
public bool Archived { get ; set ; }
2017-11-06 00:31:02 -08:00
2021-08-03 17:03:00 +09:00
[JsonConverter(typeof(StringEnumConverter))]
2024-04-04 16:31:04 +09:00
[JsonProperty]
2021-08-03 17:03:00 +09:00
public InvoiceType Type { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonIgnore]
2022-06-03 12:08:16 +02:00
public List < RefundData > Refunds { get ; set ; }
2023-01-06 14:18:07 +01:00
2024-04-04 16:31:04 +09:00
[JsonProperty]
2022-07-06 14:14:55 +02:00
public InvoiceDataBase . ReceiptOptions ReceiptOptions { get ; set ; }
2022-06-03 12:08:16 +02:00
2024-04-04 16:31:04 +09:00
[JsonProperty]
2023-04-24 23:58:58 +09:00
public bool LazyPaymentMethods { get ; set ; }
2022-11-02 10:21:33 +01:00
2017-10-27 17:53:04 +09:00
public bool IsExpired ( )
{
return DateTimeOffset . UtcNow > ExpirationTime ;
}
2017-09-13 15:47:34 +09:00
2024-04-04 16:31:04 +09:00
public InvoiceResponse EntityToDTO ( IDictionary < PaymentMethodId , IPaymentMethodBitpayAPIExtension > bitpayExtensions )
{
return EntityToDTO ( bitpayExtensions , null ) ;
}
public InvoiceResponse EntityToDTO ( IDictionary < PaymentMethodId , IPaymentMethodBitpayAPIExtension > bitpayExtensions , IUrlHelper urlHelper )
2017-10-27 17:53:04 +09:00
{
ServerUrl = ServerUrl ? ? "" ;
InvoiceResponse dto = new InvoiceResponse
{
Id = Id ,
2019-01-05 19:47:39 +01:00
StoreId = StoreId ,
2020-08-25 14:33:00 +09:00
OrderId = Metadata . OrderId ,
2023-02-25 23:34:49 +09:00
PosData = Metadata . PosDataLegacy ,
2017-10-27 17:53:04 +09:00
CurrentTime = DateTimeOffset . UtcNow ,
InvoiceTime = InvoiceTime ,
ExpirationTime = ExpirationTime ,
2024-05-15 07:49:53 +09:00
Status = Status . ToLegacyStatusString ( ) ,
ExceptionStatus = ExceptionStatus = = InvoiceExceptionStatus . None ? new JValue ( false ) : new JValue ( ExceptionStatus . ToLegacyExceptionStatusString ( ) ) ,
2020-08-25 14:33:00 +09:00
Currency = Currency ,
2020-01-12 15:32:26 +09:00
PaymentSubtotals = new Dictionary < string , decimal > ( ) ,
PaymentTotals = new Dictionary < string , decimal > ( ) ,
2021-10-25 08:18:02 +02:00
SupportedTransactionCurrencies = new Dictionary < string , NBitpayClient . InvoiceSupportedTransactionCurrency > ( ) ,
2018-05-11 11:31:21 +02:00
Addresses = new Dictionary < string , string > ( ) ,
2021-10-25 08:18:02 +02:00
PaymentCodes = new Dictionary < string , InvoiceCryptoInfo . InvoicePaymentUrls > ( ) ,
2018-05-11 11:31:21 +02:00
ExchangeRates = new Dictionary < string , Dictionary < string , decimal > > ( )
2017-10-27 17:53:04 +09:00
} ;
2018-05-04 22:05:40 +09:00
dto . Url = ServerUrl . WithTrailingSlash ( ) + $"invoice?id=" + Id ;
2021-10-25 08:18:02 +02:00
dto . CryptoInfo = new List < InvoiceCryptoInfo > ( ) ;
2018-05-25 22:49:49 +09:00
dto . MinerFees = new Dictionary < string , MinerFeeInfo > ( ) ;
2024-04-04 16:31:04 +09:00
foreach ( var info in this . GetPaymentPrompts ( ) )
2017-12-21 15:52:04 +09:00
{
2017-12-21 18:01:26 +09:00
var accounting = info . Calculate ( ) ;
2021-10-25 08:18:02 +02:00
var cryptoInfo = new InvoiceCryptoInfo ( ) ;
2024-04-04 16:31:04 +09:00
var subtotalPrice = accounting . TotalDue - accounting . PaymentMethodFee ;
var cryptoCode = info . Currency ;
var address = info . Destination ;
2018-05-11 11:31:21 +02:00
var exrates = new Dictionary < string , decimal >
{
2020-08-25 14:33:00 +09:00
{ Currency , cryptoInfo . Rate }
2018-05-11 11:31:21 +02:00
} ;
cryptoInfo . CryptoCode = cryptoCode ;
2024-04-04 16:31:04 +09:00
cryptoInfo . PaymentType = info . PaymentMethodId . ToString ( ) ;
2017-12-21 15:52:04 +09:00
cryptoInfo . Rate = info . Rate ;
2023-10-18 19:07:30 +09:00
cryptoInfo . Price = subtotalPrice . ToString ( CultureInfo . InvariantCulture ) ;
2017-12-21 18:01:26 +09:00
2023-10-18 19:07:30 +09:00
cryptoInfo . Due = accounting . Due . ToString ( CultureInfo . InvariantCulture ) ;
cryptoInfo . Paid = accounting . Paid . ToString ( CultureInfo . InvariantCulture ) ;
cryptoInfo . TotalDue = accounting . TotalDue . ToString ( CultureInfo . InvariantCulture ) ;
2024-04-04 16:31:04 +09:00
cryptoInfo . NetworkFee = accounting . PaymentMethodFee . ToString ( CultureInfo . InvariantCulture ) ;
2017-12-21 18:01:26 +09:00
cryptoInfo . TxCount = accounting . TxCount ;
2024-04-04 16:31:04 +09:00
cryptoInfo . CryptoPaid = accounting . PaymentMethodPaid . ToString ( CultureInfo . InvariantCulture ) ;
2017-12-21 18:01:26 +09:00
2018-05-11 11:31:21 +02:00
cryptoInfo . Address = address ;
2018-05-16 10:40:22 -05:00
2018-05-11 11:31:21 +02:00
cryptoInfo . ExRates = exrates ;
2024-04-04 16:31:04 +09:00
var paymentId = info . PaymentMethodId ;
2018-05-04 22:05:40 +09:00
cryptoInfo . Url = ServerUrl . WithTrailingSlash ( ) + $"i/{paymentId}/{Id}" ;
2024-04-04 16:31:04 +09:00
cryptoInfo . Payments = GetPayments ( info . Currency , true ) . Select ( entity = >
2019-04-08 15:28:17 +02:00
{
return new InvoicePaymentInfo ( )
{
2024-04-04 16:31:04 +09:00
Id = entity . Id ,
Fee = entity . PaymentMethodFee ,
Value = entity . Value ,
Completed = entity . Status is PaymentStatus . Settled ,
Confirmed = entity . Status is PaymentStatus . Settled ,
Destination = entity . Destination ,
PaymentType = entity . PaymentMethodId . ToString ( ) ,
2019-04-08 15:28:17 +02:00
ReceivedDate = entity . ReceivedTime . DateTime
} ;
2019-04-11 12:00:10 +02:00
} ) . ToList ( ) ;
2020-06-28 17:55:27 +09:00
2019-06-04 10:11:52 +09:00
2024-04-04 16:31:04 +09:00
if ( info . Activated )
2019-06-04 10:11:52 +09:00
{
2021-10-25 08:18:02 +02:00
2024-04-04 16:31:04 +09:00
if ( bitpayExtensions . TryGetValue ( paymentId , out var e ) )
e . PopulateCryptoInfo ( cryptoInfo , dto , info , urlHelper ) ;
2019-06-04 10:11:52 +09:00
}
2020-06-28 17:55:27 +09:00
2018-05-03 03:32:42 +09:00
dto . CryptoInfo . Add ( cryptoInfo ) ;
2024-04-04 16:31:04 +09:00
// Ideally, this should just be the payment id, but this
// is for legacy compatibility with the Bitpay API
var paymentCode = GetPaymentCode ( info . Currency , paymentId ) ;
dto . PaymentCodes . Add ( paymentCode , cryptoInfo . PaymentUrls ) ;
dto . PaymentSubtotals . Add ( paymentCode , accounting . ToSmallestUnit ( subtotalPrice ) ) ;
dto . PaymentTotals . Add ( paymentCode , accounting . ToSmallestUnit ( accounting . TotalDue ) ) ;
2018-05-16 10:40:25 +09:00
dto . SupportedTransactionCurrencies . TryAdd ( cryptoCode , new InvoiceSupportedTransactionCurrency ( )
2018-05-11 11:31:21 +02:00
{
Enabled = true
} ) ;
2024-04-04 16:31:04 +09:00
dto . Addresses . Add ( paymentCode , address ) ;
2018-05-16 10:40:25 +09:00
dto . ExchangeRates . TryAdd ( cryptoCode , exrates ) ;
2018-05-11 11:31:21 +02:00
}
2018-05-11 12:21:25 +02:00
2018-05-15 16:18:26 +02:00
//dto.AmountPaid dto.MinerFees & dto.TransactionCurrency are not supported by btcpayserver as we have multi currency payment support per invoice
2018-05-13 09:47:42 +02:00
2020-08-25 14:33:00 +09:00
dto . ItemCode = Metadata . ItemCode ;
dto . ItemDesc = Metadata . ItemDesc ;
dto . TaxIncluded = Metadata . TaxIncluded ? ? 0 m ;
dto . Price = Price ;
dto . Currency = Currency ;
2018-11-05 17:36:56 +09:00
dto . Buyer = new JObject ( ) ;
2020-08-25 14:33:00 +09:00
dto . Buyer . Add ( new JProperty ( "name" , Metadata . BuyerName ) ) ;
dto . Buyer . Add ( new JProperty ( "address1" , Metadata . BuyerAddress1 ) ) ;
dto . Buyer . Add ( new JProperty ( "address2" , Metadata . BuyerAddress2 ) ) ;
dto . Buyer . Add ( new JProperty ( "locality" , Metadata . BuyerCity ) ) ;
dto . Buyer . Add ( new JProperty ( "region" , Metadata . BuyerState ) ) ;
dto . Buyer . Add ( new JProperty ( "postalCode" , Metadata . BuyerZip ) ) ;
dto . Buyer . Add ( new JProperty ( "country" , Metadata . BuyerCountry ) ) ;
dto . Buyer . Add ( new JProperty ( "phone" , Metadata . BuyerPhone ) ) ;
2024-04-04 16:31:04 +09:00
dto . Buyer . Add ( new JProperty ( "email" , Metadata . BuyerEmail ) ) ;
2017-12-21 15:52:04 +09:00
2017-10-27 17:53:04 +09:00
dto . Token = Encoders . Base58 . EncodeData ( RandomUtils . GetBytes ( 16 ) ) ; //No idea what it is useful for
dto . Guid = Guid . NewGuid ( ) . ToString ( ) ;
return dto ;
}
2017-09-26 01:31:43 +09:00
2024-04-04 16:31:04 +09:00
private static string GetPaymentCode ( string currency , PaymentMethodId paymentId )
{
return PaymentTypes . CHAIN . GetPaymentMethodId ( currency ) = = paymentId ? currency : paymentId . ToString ( ) ;
}
#nullable enable
2018-02-19 15:09:05 +09:00
internal bool Support ( PaymentMethodId paymentMethodId )
2017-12-21 15:52:04 +09:00
{
2024-04-04 16:31:04 +09:00
var rates = GetPaymentPrompts ( ) ;
2018-02-19 15:09:05 +09:00
return rates . TryGet ( paymentMethodId ) ! = null ;
2017-12-21 15:52:04 +09:00
}
2024-04-04 16:31:04 +09:00
public PaymentPrompt ? GetPaymentPrompt ( PaymentMethodId paymentMethodId )
2017-12-21 15:52:04 +09:00
{
2024-04-04 16:31:04 +09:00
#pragma warning disable CS0618 // Type or member is obsolete
if ( PaymentPrompts is null )
return null ;
var pm = PaymentPrompts [ paymentMethodId . ToString ( ) ] ;
#pragma warning restore CS0618 // Type or member is obsolete
if ( pm is null or JToken { Type : JTokenType . Null } )
return null ;
var r = pm . ToObject < PaymentPrompt > ( InvoiceDataExtensions . DefaultSerializer ) ! ;
r . ParentEntity = this ;
r . PaymentMethodId = paymentMethodId ;
return r ;
2017-12-21 15:52:04 +09:00
}
2024-04-04 16:31:04 +09:00
public PaymentPromptDictionary GetPaymentPrompts ( )
2017-12-21 15:52:04 +09:00
{
2024-04-04 16:31:04 +09:00
PaymentPromptDictionary paymentMethods = new PaymentPromptDictionary ( ) ;
2017-12-21 15:52:04 +09:00
#pragma warning disable CS0618
2024-04-04 16:31:04 +09:00
if ( PaymentPrompts ! = null )
2017-12-21 15:52:04 +09:00
{
2024-04-04 16:31:04 +09:00
foreach ( var prop in PaymentPrompts . Properties ( ) )
2017-12-21 15:52:04 +09:00
{
2020-08-09 14:43:13 +02:00
if ( ! PaymentMethodId . TryParse ( prop . Name , out var paymentMethodId ) )
{
continue ;
}
2024-04-04 16:31:04 +09:00
if ( prop . Value ? . Type is not JTokenType . Object )
2020-07-30 11:36:02 +09:00
{
2024-04-04 16:31:04 +09:00
continue ;
2020-07-30 11:36:02 +09:00
}
2024-04-04 16:31:04 +09:00
var r = prop . Value . ToObject < PaymentPrompt > ( InvoiceDataExtensions . DefaultSerializer ) ! ;
r . ParentEntity = this ;
r . PaymentMethodId = paymentMethodId ;
2019-06-04 01:40:23 +09:00
paymentMethods . Add ( r ) ;
2017-12-21 15:52:04 +09:00
}
}
#pragma warning restore CS0618
2019-01-05 13:31:05 +09:00
return paymentMethods ;
2017-12-21 15:52:04 +09:00
}
2024-04-04 16:31:04 +09:00
public void SetPaymentPrompt ( PaymentMethodId paymentMethodId , PaymentPrompt paymentMethod )
2018-01-11 17:29:48 +09:00
{
2024-04-04 16:31:04 +09:00
var dict = GetPaymentPrompts ( ) ;
paymentMethod . PaymentMethodId = paymentMethodId ;
paymentMethod . ParentEntity = this ;
2018-02-19 15:09:05 +09:00
dict . AddOrReplace ( paymentMethod ) ;
2024-04-04 16:31:04 +09:00
SetPaymentPrompts ( dict ) ;
2018-01-11 17:29:48 +09:00
}
2024-04-04 16:31:04 +09:00
public void SetPaymentPrompts ( PaymentPromptDictionary paymentMethods )
2017-12-21 15:52:04 +09:00
{
var obj = new JObject ( ) ;
2018-02-19 02:38:03 +09:00
#pragma warning disable CS0618
2018-02-19 15:09:05 +09:00
foreach ( var v in paymentMethods )
2017-12-21 15:52:04 +09:00
{
2024-04-04 16:31:04 +09:00
obj . Add ( new JProperty ( v . PaymentMethodId . ToString ( ) , JToken . FromObject ( v , InvoiceDataExtensions . DefaultSerializer ) ) ) ;
2017-12-21 15:52:04 +09:00
}
2024-04-04 16:31:04 +09:00
PaymentPrompts = obj ;
2018-02-26 00:48:12 +09:00
foreach ( var cryptoData in paymentMethods )
2018-02-19 18:54:21 +09:00
{
cryptoData . ParentEntity = this ;
}
2017-12-21 15:52:04 +09:00
#pragma warning restore CS0618
2023-07-19 18:47:32 +09:00
UpdateTotals ( ) ;
2017-12-21 15:52:04 +09:00
}
2024-04-04 16:31:04 +09:00
#nullable restore
2018-12-10 15:34:48 +09:00
public InvoiceState GetInvoiceState ( )
{
2018-12-10 21:48:28 +09:00
return new InvoiceState ( Status , ExceptionStatus ) ;
2018-12-10 15:34:48 +09:00
}
2020-08-25 14:33:00 +09:00
2021-08-03 17:03:00 +09:00
public bool IsUnsetTopUp ( )
{
return Type = = InvoiceType . TopUp & & Price = = 0.0 m ;
}
2024-04-04 16:31:04 +09:00
[JsonIgnore]
2023-07-19 18:47:32 +09:00
public Amounts PaidAmount { get ; set ; }
2018-12-10 15:34:48 +09:00
}
2020-11-23 15:57:05 +09:00
public enum InvoiceStatusLegacy
{
}
public static class InvoiceStatusLegacyExtensions
{
2024-05-15 07:49:53 +09:00
public static string ToLegacyStatusString ( this InvoiceStatus status ) = >
status switch
2020-11-23 15:57:05 +09:00
{
2024-05-15 07:49:53 +09:00
InvoiceStatus . Settled = > "complete" ,
InvoiceStatus . Expired = > "expired" ,
InvoiceStatus . Invalid = > "invalid" ,
InvoiceStatus . Processing = > "paid" ,
InvoiceStatus . New = > "new" ,
_ = > throw new NotSupportedException ( status . ToString ( ) )
} ;
public static string ToLegacyExceptionStatusString ( this InvoiceExceptionStatus status ) = >
status switch
{
InvoiceExceptionStatus . None = > string . Empty ,
InvoiceExceptionStatus . PaidLate = > "paidLater" ,
InvoiceExceptionStatus . PaidPartial = > "paidPartial" ,
InvoiceExceptionStatus . PaidOver = > "paidOver" ,
InvoiceExceptionStatus . Marked = > "marked" ,
_ = > throw new NotSupportedException ( status . ToString ( ) )
} ;
2020-11-23 15:57:05 +09:00
}
2024-05-15 07:49:53 +09:00
public record InvoiceState ( InvoiceStatus Status , InvoiceExceptionStatus ExceptionStatus )
2018-12-10 15:34:48 +09:00
{
2024-05-15 07:49:53 +09:00
public InvoiceState ( string status , string exceptionStatus ) :
this ( Enum . Parse < InvoiceStatus > ( status ) , exceptionStatus switch { "None" or "" or null = > InvoiceExceptionStatus . None , _ = > Enum . Parse < InvoiceExceptionStatus > ( exceptionStatus ) } )
2018-12-10 21:48:28 +09:00
{
}
2018-12-10 15:34:48 +09:00
public bool CanMarkComplete ( )
{
2024-05-15 07:49:53 +09:00
return Status is InvoiceStatus . New or InvoiceStatus . Processing or InvoiceStatus . Expired or InvoiceStatus . Invalid | |
( Status ! = InvoiceStatus . Settled & & ExceptionStatus = = InvoiceExceptionStatus . Marked ) ;
2018-12-10 15:34:48 +09:00
}
public bool CanMarkInvalid ( )
{
2024-05-15 07:49:53 +09:00
return Status is InvoiceStatus . New or InvoiceStatus . Processing or InvoiceStatus . Expired | |
( Status ! = InvoiceStatus . Invalid & & ExceptionStatus = = InvoiceExceptionStatus . Marked ) ;
2018-12-10 15:34:48 +09:00
}
2020-10-23 21:00:23 +09:00
2022-11-28 00:53:08 -08:00
public bool CanRefund ( )
{
2024-05-15 07:49:53 +09:00
return
Status = = InvoiceStatus . Settled | |
( Status = = InvoiceStatus . Expired & &
2022-11-28 00:53:08 -08:00
( ExceptionStatus = = InvoiceExceptionStatus . PaidLate | |
ExceptionStatus = = InvoiceExceptionStatus . PaidOver | |
ExceptionStatus = = InvoiceExceptionStatus . PaidPartial ) ) | |
2024-05-15 07:49:53 +09:00
Status = = InvoiceStatus . Invalid ;
2022-11-28 00:53:08 -08:00
}
2023-09-19 03:10:13 +02:00
public bool IsSettled ( )
{
2024-05-15 07:49:53 +09:00
return
Status = = InvoiceStatus . Settled | |
( Status = = InvoiceStatus . Expired & &
2023-09-19 03:10:13 +02:00
ExceptionStatus is InvoiceExceptionStatus . PaidLate or InvoiceExceptionStatus . PaidOver ) ;
}
2018-12-10 15:34:48 +09:00
public override string ToString ( )
{
2024-05-15 07:49:53 +09:00
return Status + ExceptionStatus switch
2023-10-11 16:12:45 +02:00
{
InvoiceExceptionStatus . PaidOver = > " (paid over)" ,
InvoiceExceptionStatus . PaidLate = > " (paid late)" ,
InvoiceExceptionStatus . PaidPartial = > " (paid partial)" ,
InvoiceExceptionStatus . Marked = > " (marked)" ,
_ = > ""
} ;
2018-12-10 15:34:48 +09:00
}
2017-12-21 15:52:04 +09:00
}
2018-02-19 15:09:05 +09:00
public class PaymentMethodAccounting
2017-12-21 18:01:26 +09:00
{
2023-07-19 18:47:32 +09:00
public int Divisibility { get ; set ; }
2018-11-30 01:22:39 -06:00
/// <summary>Total amount of this invoice</summary>
2023-07-19 18:47:32 +09:00
public decimal TotalDue { get ; set ; }
2017-12-21 18:01:26 +09:00
2018-11-30 01:22:39 -06:00
/// <summary>Amount of crypto remaining to pay this invoice</summary>
2023-07-19 18:47:32 +09:00
public decimal Due { get ; set ; }
2017-12-21 18:01:26 +09:00
2018-11-30 01:22:39 -06:00
/// <summary>Same as Due, can be negative</summary>
2023-07-19 18:47:32 +09:00
public decimal DueUncapped { get ; set ; }
2018-11-30 01:22:39 -06:00
/// <summary>If DueUncapped is negative, that means user overpaid invoice</summary>
2023-07-19 18:47:32 +09:00
public decimal OverpaidHelper
2018-11-30 01:22:39 -06:00
{
2023-07-19 18:47:32 +09:00
get { return DueUncapped > 0.0 m ? 0.0 m : - DueUncapped ; }
2018-11-30 01:22:39 -06:00
}
2017-12-21 18:01:26 +09:00
/// <summary>
/// Total amount of the invoice paid after conversion to this crypto currency
/// </summary>
2023-07-19 18:47:32 +09:00
public decimal Paid { get ; set ; }
2017-12-21 18:01:26 +09:00
/// <summary>
/// Total amount of the invoice paid in this currency
/// </summary>
2024-04-04 16:31:04 +09:00
public decimal PaymentMethodPaid { get ; set ; }
2017-12-21 18:01:26 +09:00
/// <summary>
/// Number of transactions required to pay
/// </summary>
2018-02-26 00:48:12 +09:00
public int TxRequired { get ; set ; }
/// <summary>
/// Number of transactions using this payment method
/// </summary>
2017-12-21 18:01:26 +09:00
public int TxCount { get ; set ; }
/// <summary>
2024-04-04 16:31:04 +09:00
/// Amount of fee already paid + to be paid in the invoice's currency
2017-12-21 18:01:26 +09:00
/// </summary>
2024-04-04 16:31:04 +09:00
public decimal PaymentMethodFee { get ; set ; }
2018-05-05 16:07:22 +02:00
/// <summary>
2024-04-04 16:31:04 +09:00
/// Amount of fee already paid in the invoice's currency
2021-08-03 17:03:00 +09:00
/// </summary>
2024-04-04 16:31:04 +09:00
public decimal PaymentMethodFeeAlreadyPaid { get ; set ; }
2021-08-03 17:03:00 +09:00
/// <summary>
2018-12-18 11:56:12 -06:00
/// Minimum required to be paid in order to accept invoice as paid
2018-05-05 16:07:22 +02:00
/// </summary>
2023-07-19 18:47:32 +09:00
public decimal MinimumTotalDue { get ; set ; }
public decimal ToSmallestUnit ( decimal v )
{
for ( int i = 0 ; i < Divisibility ; i + + )
{
v * = 10.0 m ;
}
return v ;
}
public string ShowMoney ( decimal v ) = > MoneyExtensions . ShowMoney ( v , Divisibility ) ;
2017-12-21 18:01:26 +09:00
}
2024-04-04 16:31:04 +09:00
public class PaymentPrompt
2017-12-21 15:52:04 +09:00
{
2024-04-04 16:31:04 +09:00
[JsonIgnore]
public bool Activated = > ! Inactive ;
public bool Inactive { get ; set ; }
2017-12-21 15:52:04 +09:00
[JsonIgnore]
public InvoiceEntity ParentEntity { get ; set ; }
2018-01-11 17:29:48 +09:00
[JsonIgnore]
2024-04-04 16:31:04 +09:00
public PaymentMethodId PaymentMethodId { get ; set ; }
2023-07-19 18:47:32 +09:00
public string Currency { get ; set ; }
2019-03-17 21:28:47 +09:00
[JsonIgnore]
2024-04-04 16:31:04 +09:00
public decimal Rate = > Currency is null ? throw new InvalidOperationException ( "Currency of the payment prompt isn't set" ) : ParentEntity . GetInvoiceRate ( Currency ) ;
public int Divisibility { get ; set ; }
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal PaymentMethodFee { get ; set ; }
public string Destination { get ; set ; }
public JToken Details { get ; set ; }
2018-01-06 18:57:56 +09:00
2023-07-19 18:47:32 +09:00
public PaymentMethodAccounting Calculate ( )
2017-12-21 15:52:04 +09:00
{
2023-07-19 18:47:32 +09:00
var i = ParentEntity ;
2018-02-26 00:48:12 +09:00
var accounting = new PaymentMethodAccounting ( ) ;
2024-04-04 16:31:04 +09:00
var thisPaymentMethodPayments = i . GetPayments ( true ) . Where ( p = > PaymentMethodId = = p . PaymentMethodId ) . ToList ( ) ;
2023-07-19 18:47:32 +09:00
accounting . TxCount = thisPaymentMethodPayments . Count ;
accounting . TxRequired = accounting . TxCount ;
var grossDue = i . Price + i . PaidFee ;
2024-04-04 16:31:04 +09:00
var rate = Rate ;
2023-07-19 18:47:32 +09:00
if ( i . MinimumNetDue > 0.0 m )
2018-01-09 10:54:19 +09:00
{
2023-07-19 18:47:32 +09:00
accounting . TxRequired + + ;
2024-04-04 16:31:04 +09:00
grossDue + = rate * PaymentMethodFee ;
2018-01-09 10:54:19 +09:00
}
2024-04-04 16:31:04 +09:00
accounting . Divisibility = Divisibility ;
accounting . TotalDue = Coins ( grossDue / rate , Divisibility ) ;
accounting . Paid = Coins ( i . PaidAmount . Gross / rate , Divisibility ) ;
accounting . PaymentMethodPaid = Coins ( thisPaymentMethodPayments . Sum ( p = > p . PaidAmount . Gross ) , Divisibility ) ;
2023-07-19 18:47:32 +09:00
// This one deal with the fact where it might looks like a slight over payment due to the dust of another payment method.
// So if we detect the NetDue is zero, just cap dueUncapped to 0
var dueUncapped = i . NetDue = = 0.0 m ? 0.0 m : grossDue - i . PaidAmount . Gross ;
2024-04-04 16:31:04 +09:00
accounting . DueUncapped = Coins ( dueUncapped / rate , Divisibility ) ;
2023-07-19 18:47:32 +09:00
accounting . Due = Max ( accounting . DueUncapped , 0.0 m ) ;
2024-04-04 16:31:04 +09:00
accounting . PaymentMethodFee = Coins ( ( grossDue - i . Price ) / rate , Divisibility ) ;
accounting . PaymentMethodFeeAlreadyPaid = Coins ( i . PaidFee / rate , Divisibility ) ;
accounting . MinimumTotalDue = Max ( Smallest ( Divisibility ) , Coins ( ( grossDue * ( 1.0 m - ( ( decimal ) i . PaymentTolerance / 100.0 m ) ) ) / rate , Divisibility ) ) ;
2017-12-21 18:01:26 +09:00
return accounting ;
2017-12-21 15:52:04 +09:00
}
2018-01-06 18:57:56 +09:00
2023-07-19 18:47:32 +09:00
private decimal Smallest ( int precision )
2023-06-16 10:47:58 +09:00
{
2023-07-19 18:47:32 +09:00
decimal a = 1.0 m ;
for ( int i = 0 ; i < precision ; i + + )
{
a / = 10.0 m ;
}
return a ;
2023-06-16 10:47:58 +09:00
}
2023-07-19 18:47:32 +09:00
decimal Max ( decimal a , decimal b ) = > a > b ? a : b ;
const decimal MaxCoinValue = decimal . MaxValue / 1_0000_0000 m ;
internal static decimal Coins ( decimal v , int precision )
2018-02-19 02:38:03 +09:00
{
2023-07-19 18:47:32 +09:00
v = Extensions . RoundUp ( v , precision ) ;
// Clamp the value to not crash on degenerate invoices
if ( v > MaxCoinValue )
2024-04-04 16:31:04 +09:00
v = MaxCoinValue ;
2023-07-19 18:47:32 +09:00
return v ;
2018-02-19 02:38:03 +09:00
}
2017-10-27 17:53:04 +09:00
}
2017-09-13 15:47:34 +09:00
2024-04-04 16:31:04 +09:00
public class PaymentEntity : PaymentBlob
2017-10-27 17:53:04 +09:00
{
2020-04-05 23:57:43 +09:00
[JsonIgnore]
public DateTimeOffset ReceivedTime
{
2024-04-04 16:31:04 +09:00
get ;
set ;
2017-11-06 00:31:02 -08:00
}
2024-04-04 16:31:04 +09:00
[JsonIgnore]
public PaymentStatus Status { get ; set ; }
[JsonIgnore]
public bool Accounted = > Status is PaymentStatus . Settled or PaymentStatus . Processing ;
2017-12-21 15:52:04 +09:00
2024-04-04 16:31:04 +09:00
[JsonIgnore]
2023-07-19 18:47:32 +09:00
public string Currency
2017-12-21 15:52:04 +09:00
{
2024-04-04 16:31:04 +09:00
get ;
set ;
2017-12-21 15:52:04 +09:00
}
2024-04-04 16:31:04 +09:00
[JsonIgnore]
public PaymentMethodId PaymentMethodId { get ; set ; }
2023-07-19 18:47:32 +09:00
[JsonIgnore]
public decimal Rate { get ; set ; }
[JsonIgnore]
/// <summary>
public string InvoiceCurrency = > InvoiceEntity . Currency ;
/// The amount paid by this payment in the <see cref="Currency"/>
/// </summary>
[JsonIgnore]
public Amounts PaidAmount { get ; set ; }
/// <summary>
/// The amount paid by this payment in the <see cref="InvoiceCurrency"/>
/// </summary>
[JsonIgnore]
public Amounts InvoicePaidAmount { get ; set ; }
[JsonIgnore]
public InvoiceEntity InvoiceEntity { get ; set ; }
2024-04-04 16:31:04 +09:00
[JsonIgnore]
public decimal Value { get ; set ; }
[JsonIgnore]
public string Id { get ; set ; }
2018-02-17 01:34:40 +09:00
2023-07-19 18:47:32 +09:00
public void UpdateAmounts ( )
{
2024-04-04 16:31:04 +09:00
var value = Value ;
2023-07-19 18:47:32 +09:00
PaidAmount = new Amounts ( )
{
Currency = Currency ,
2024-04-04 16:31:04 +09:00
Gross = Value ,
Net = Value - PaymentMethodFee
2023-07-19 18:47:32 +09:00
} ;
InvoicePaidAmount = new Amounts ( )
{
Currency = InvoiceCurrency ,
Gross = PaidAmount . Gross * Rate ,
Net = PaidAmount . Net * Rate
} ;
}
2018-02-17 01:34:40 +09:00
}
2019-05-29 14:33:31 +00:00
/// <summary>
/// A record of a payment
/// </summary>
2018-02-17 01:34:40 +09:00
public interface CryptoPaymentData
{
2023-04-10 16:38:49 +09:00
string GetPaymentProof ( ) ;
2018-02-17 01:34:40 +09:00
}
2017-09-13 15:47:34 +09:00
}