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 ;
using System.Linq ;
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 ;
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 ;
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
}
2020-11-08 23:37:45 -06:00
2019-05-24 22:22:38 +09:00
[JsonIgnore]
public BTCPayNetworkProvider Networks { get ; set ; }
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 ;
public const int Lastest_Version = 2 ;
2019-02-22 17:51:38 +09:00
public int Version { get ; set ; }
2020-11-08 23:37:45 -06:00
public string Id { get ; set ; }
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 ; }
2020-12-10 23:34:50 +09:00
public string DefaultLanguage { get ; set ; }
2018-02-19 15:09:05 +09:00
[Obsolete("Use GetPaymentMethod(network) instead")]
2023-06-16 10:47:58 +09:00
[JsonConverter(typeof(NumericStringJsonConverter))]
2020-11-08 23:37:45 -06:00
public decimal Rate { get ; set ; }
public DateTimeOffset InvoiceTime { get ; set ; }
public DateTimeOffset ExpirationTime { get ; set ; }
2017-12-21 15:52:04 +09:00
2018-02-19 15:09:05 +09:00
[Obsolete("Use GetPaymentMethod(network).GetPaymentMethodDetails().GetDestinationAddress() instead")]
2020-11-08 23:37:45 -06:00
public string DepositAddress { get ; set ; }
2018-01-06 18:57:56 +09:00
2020-11-08 23:37:45 -06:00
public InvoiceMetadata Metadata { get ; set ; }
2023-06-16 10:47:58 +09:00
[JsonConverter(typeof(NumericStringJsonConverter))]
2020-08-25 14:33:00 +09:00
public decimal Price { get ; set ; }
public string Currency { get ; set ; }
2021-08-22 21:55:06 -07:00
public string DefaultPaymentMethod { get ; set ; }
2021-10-18 16:56:47 +09:00
#nullable enable
public PaymentMethodId ? GetDefaultPaymentMethod ( )
{
PaymentMethodId . TryParse ( DefaultPaymentMethod , out var id ) ;
return id ;
}
#nullable restore
2020-08-25 14:33:00 +09:00
[JsonExtensionData]
public IDictionary < string , JToken > AdditionalData { get ; set ; }
2019-02-19 11:14:21 +09:00
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
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
2018-02-19 23:13:23 +09:00
[Obsolete("Use GetPaymentMethodFactories() instead")]
2023-02-25 23:34:49 +09:00
[JsonIgnore]
public JObject DerivationStrategies
{
get
{
if ( AdditionalData is null | | AdditionalData . TryGetValue ( "derivationStrategies" , out var v ) is not true )
{
if ( AdditionalData is null | | AdditionalData . TryGetValue ( "derivationStrategy" , out v ) is not true | | Networks . BTC is null )
return null ;
// This code is very unlikely called. "derivationStrategy" is an old property that was present in 2018.
// And this property is only read for unexpired invoices with lazy payments (Feature unavailable then)
var settings = DerivationSchemeSettings . Parse ( v . ToString ( ) , Networks . BTC ) ;
settings . AccountOriginal = v . ToString ( ) ;
settings . Source = "ManualDerivationScheme" ;
return JObject . Parse ( settings . ToJson ( ) ) ;
}
if ( v . Type = = JTokenType . String )
return JObject . Parse ( v . Value < string > ( ) ) ;
if ( v . Type = = JTokenType . Object )
return ( JObject ) v ;
return null ;
}
set
{
this . SetAdditionalData ( "derivationStrategies" , value ) ;
this . SetAdditionalData < string > ( "derivationStrategy" , null ) ;
}
}
2019-05-24 22:22:38 +09:00
public IEnumerable < T > GetSupportedPaymentMethod < T > ( PaymentMethodId paymentMethodId ) where T : ISupportedPaymentMethod
2018-02-20 14:23:50 +09:00
{
return
2019-05-24 22:22:38 +09:00
GetSupportedPaymentMethod ( )
2018-02-20 14:23:50 +09:00
. Where ( p = > paymentMethodId = = null | | p . PaymentId = = paymentMethodId )
. OfType < T > ( ) ;
}
2019-05-24 22:22:38 +09:00
public IEnumerable < T > GetSupportedPaymentMethod < T > ( ) where T : ISupportedPaymentMethod
2018-02-20 14:23:50 +09:00
{
2019-05-24 22:22:38 +09:00
return GetSupportedPaymentMethod < T > ( null ) ;
2018-02-26 00:48:12 +09:00
}
2019-05-24 22:22:38 +09:00
public IEnumerable < ISupportedPaymentMethod > GetSupportedPaymentMethod ( )
2018-01-06 18:57:56 +09:00
{
#pragma warning disable CS0618
2023-02-25 23:34:49 +09:00
if ( DerivationStrategies ! = null )
2018-01-06 18:57:56 +09:00
{
2023-02-25 23:34:49 +09:00
foreach ( var strat in DerivationStrategies . Properties ( ) )
2018-01-06 18:57:56 +09:00
{
2021-09-24 07:16:25 +02:00
if ( ! PaymentMethodId . TryParse ( strat . Name , out var paymentMethodId ) )
{
continue ;
}
2021-04-07 06:08:42 +02:00
var network = Networks . GetNetwork < BTCPayNetworkBase > ( paymentMethodId . CryptoCode ) ;
2018-01-06 18:57:56 +09:00
if ( network ! = null )
{
2019-06-04 09:33:42 +09:00
yield return paymentMethodId . PaymentType . DeserializeSupportedPaymentMethod ( network , strat . Value ) ;
2018-01-06 18:57:56 +09:00
}
}
}
#pragma warning restore CS0618
}
2018-02-20 10:44:39 +09:00
internal void SetSupportedPaymentMethods ( IEnumerable < ISupportedPaymentMethod > derivationStrategies )
2018-01-06 18:57:56 +09:00
{
JObject obj = new JObject ( ) ;
2018-01-09 10:54:19 +09:00
foreach ( var strat in derivationStrategies )
2018-01-06 18:57:56 +09:00
{
2018-02-20 12:45:04 +09:00
obj . Add ( strat . PaymentId . ToString ( ) , PaymentMethodExtensions . Serialize ( strat ) ) ;
2018-01-06 18:57:56 +09:00
#pragma warning disable CS0618
}
2023-02-25 23:34:49 +09:00
DerivationStrategies = obj ;
2018-01-06 18:57:56 +09:00
#pragma warning restore CS0618
}
2023-07-19 18:47:32 +09:00
[JsonIgnore]
public Dictionary < string , decimal > Rates
{
get ;
private set ;
}
public void UpdateTotals ( )
{
Rates = new Dictionary < string , decimal > ( ) ;
foreach ( var p in GetPaymentMethods ( ) )
{
Rates . TryAdd ( p . Currency , p . Rate ) ;
}
PaidAmount = new Amounts ( )
{
Currency = Currency
} ;
foreach ( var payment in GetPayments ( false ) )
{
payment . Rate = Rates [ payment . Currency ] ;
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
if ( GetPaymentMethods ( ) . Any ( p = > p . Calculate ( ) . DueUncapped = = 0.0 m ) )
{
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>
/// Minumum due to consider the invoice paid (can be negative if overpaid)
/// </summary>
[JsonIgnore]
public decimal MinimumNetDue { get ; set ; }
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]
2020-11-23 15:57:05 +09:00
public InvoiceStatusLegacy Status { get ; set ; }
2018-12-10 21:48:28 +09:00
[JsonProperty(PropertyName = "status")]
[Obsolete("Use Status instead")]
public string StatusString = > InvoiceState . ToString ( Status ) ;
[JsonIgnore]
2020-11-08 23:37:45 -06:00
public InvoiceExceptionStatus ExceptionStatus { get ; set ; }
2018-12-10 21:48:28 +09:00
[JsonProperty(PropertyName = "exceptionStatus")]
[Obsolete("Use ExceptionStatus instead")]
public string ExceptionStatusString = > InvoiceState . ToString ( ExceptionStatus ) ;
2018-01-10 18:30:45 +09:00
[Obsolete("Use GetPayments instead")]
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
{
2021-05-14 16:16:19 +09:00
return Payments ? . Where ( entity = > entity . GetPaymentMethodId ( ) ! = null & & ( ! accountedOnly | | entity . Accounted ) ) . ToList ( ) ? ? new List < PaymentEntity > ( ) ;
2018-01-10 18:30:45 +09:00
}
2021-05-14 16:16:19 +09:00
public List < PaymentEntity > GetPayments ( string cryptoCode , bool accountedOnly )
2018-01-10 18:30:45 +09:00
{
2023-07-19 18:47:32 +09:00
return GetPayments ( accountedOnly ) . Where ( p = > p . Currency = = cryptoCode ) . ToList ( ) ;
2018-01-10 18:30:45 +09:00
}
2021-05-14 16:16:19 +09:00
public List < PaymentEntity > GetPayments ( BTCPayNetworkBase network , bool accountedOnly )
2018-01-10 18:30:45 +09:00
{
2021-05-14 16:16:19 +09:00
return GetPayments ( network . CryptoCode , accountedOnly ) ;
2018-01-10 18:30:45 +09:00
}
#pragma warning restore CS0618
2022-07-01 06:26:00 +02:00
// public bool Refundable { get; set; }
2021-10-27 07:32:56 -07:00
public bool? RequiresRefundEmail { get ; set ; } = null ;
2020-11-08 23:37:45 -06:00
public string RefundMail { get ; set ; }
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
}
2020-11-08 23:37:45 -06:00
public bool RedirectAutomatically { get ; set ; }
2017-12-21 15:52:04 +09:00
2018-02-19 15:09:05 +09:00
[Obsolete("Use GetPaymentMethod(network).GetTxFee() instead")]
2020-11-08 23:37:45 -06:00
public Money TxFee { get ; set ; }
public bool FullNotifications { get ; set ; }
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
2018-02-19 15:09:05 +09:00
[Obsolete("Use Set/GetPaymentMethod() instead")]
[JsonProperty(PropertyName = "cryptoData")]
public JObject PaymentMethod { get ; set ; }
2017-12-21 15:52:04 +09:00
2017-12-03 14:43:52 +09:00
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
2020-11-08 23:37:45 -06:00
public DateTimeOffset MonitoringExpiration { get ; set ; }
2017-09-13 15:47:34 +09:00
2020-11-08 23:37:45 -06:00
public HashSet < string > AvailableAddressHashes { get ; set ; }
2018-01-08 04:14:35 +09:00
public bool ExtendedNotifications { get ; set ; }
2018-01-14 21:48:23 +09:00
public List < InvoiceEventData > Events { get ; internal set ; }
2018-05-04 16:15:34 +02:00
public double PaymentTolerance { get ; set ; }
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))]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public InvoiceType Type { get ; set ; }
2022-06-03 12:08:16 +02:00
public List < RefundData > Refunds { get ; set ; }
2023-01-06 14:18:07 +01:00
2022-07-06 14:14:55 +02:00
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public InvoiceDataBase . ReceiptOptions ReceiptOptions { get ; set ; }
2022-06-03 12:08:16 +02:00
2022-11-02 10:21:33 +01:00
[JsonConverter(typeof(StringEnumConverter))]
public CheckoutType ? CheckoutType { get ; set ; }
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
2019-05-24 22:22:38 +09:00
public InvoiceResponse EntityToDTO ( )
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 ,
2018-12-10 21:48:28 +09:00
#pragma warning disable CS0618 // Type or member is obsolete
Status = StatusString ,
ExceptionStatus = ExceptionStatus = = InvoiceExceptionStatus . None ? new JValue ( false ) : new JValue ( ExceptionStatusString ) ,
#pragma warning restore CS0618 // Type or member is obsolete
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 > ( ) ;
2019-05-24 22:22:38 +09:00
foreach ( var info in this . GetPaymentMethods ( ) )
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 ( ) ;
2018-05-11 12:21:25 +02:00
var subtotalPrice = accounting . TotalDue - accounting . NetworkFee ;
2018-05-11 11:31:21 +02:00
var cryptoCode = info . GetId ( ) . CryptoCode ;
2020-01-06 13:57:32 +01:00
var details = info . GetPaymentMethodDetails ( ) ;
var address = details ? . GetPaymentDestination ( ) ;
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 ;
2018-02-19 02:38:03 +09:00
cryptoInfo . PaymentType = info . GetId ( ) . PaymentType . ToString ( ) ;
2017-12-21 15:52:04 +09:00
cryptoInfo . Rate = info . Rate ;
2018-05-11 12:21:25 +02:00
cryptoInfo . Price = subtotalPrice . ToString ( ) ;
2017-12-21 18:01:26 +09:00
cryptoInfo . Due = accounting . Due . ToString ( ) ;
cryptoInfo . Paid = accounting . Paid . ToString ( ) ;
cryptoInfo . TotalDue = accounting . TotalDue . ToString ( ) ;
cryptoInfo . NetworkFee = accounting . NetworkFee . ToString ( ) ;
cryptoInfo . TxCount = accounting . TxCount ;
2018-01-11 17:29:48 +09:00
cryptoInfo . CryptoPaid = accounting . CryptoPaid . ToString ( ) ;
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 ;
2018-05-04 22:05:40 +09:00
var paymentId = info . GetId ( ) ;
cryptoInfo . Url = ServerUrl . WithTrailingSlash ( ) + $"i/{paymentId}/{Id}" ;
2017-12-21 15:52:04 +09:00
2021-05-14 16:16:19 +09:00
cryptoInfo . Payments = GetPayments ( info . Network , true ) . Select ( entity = >
2019-04-08 15:28:17 +02:00
{
var data = entity . GetCryptoPaymentData ( ) ;
return new InvoicePaymentInfo ( )
{
Id = data . GetPaymentId ( ) ,
Fee = entity . NetworkFee ,
Value = data . GetValue ( ) ,
2019-06-07 13:24:36 +09:00
Completed = data . PaymentCompleted ( entity ) ,
Confirmed = data . PaymentConfirmed ( entity , SpeedPolicy ) ,
Destination = data . GetDestination ( ) ,
2019-04-08 15:28:17 +02:00
PaymentType = data . GetPaymentType ( ) . ToString ( ) ,
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
2021-10-25 08:18:02 +02:00
if ( details ? . Activated is true )
2019-06-04 10:11:52 +09:00
{
2021-10-25 08:18:02 +02:00
2023-04-10 16:38:49 +09:00
paymentId . PaymentType . PopulateCryptoInfo ( this , info , cryptoInfo , ServerUrl ) ;
2021-10-25 08:18:02 +02:00
if ( paymentId . PaymentType = = PaymentTypes . BTCLike )
2019-06-04 10:11:52 +09:00
{
2021-10-25 08:18:02 +02:00
var minerInfo = new MinerFeeInfo ( ) ;
2023-07-19 18:47:32 +09:00
minerInfo . TotalFee = accounting . ToSmallestUnit ( accounting . NetworkFee ) ;
2021-10-25 08:18:02 +02:00
minerInfo . SatoshiPerBytes = ( ( BitcoinLikeOnChainPaymentMethod ) details ) . FeeRate
. GetFee ( 1 ) . Satoshi ;
dto . MinerFees . TryAdd ( cryptoInfo . CryptoCode , minerInfo ) ;
2019-06-04 10:11:52 +09:00
#pragma warning disable 618
2023-07-19 18:47:32 +09:00
if ( info . Currency = = "BTC" )
2021-10-25 08:18:02 +02:00
{
dto . BTCPrice = cryptoInfo . Price ;
dto . Rate = cryptoInfo . Rate ;
dto . ExRates = cryptoInfo . ExRates ;
dto . BitcoinAddress = cryptoInfo . Address ;
dto . BTCPaid = cryptoInfo . Paid ;
dto . BTCDue = cryptoInfo . Due ;
dto . PaymentUrls = cryptoInfo . PaymentUrls ;
}
2019-06-04 10:11:52 +09:00
#pragma warning restore 618
2021-10-25 08:18:02 +02:00
}
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 ) ;
2018-05-16 10:40:25 +09:00
dto . PaymentCodes . Add ( paymentId . ToString ( ) , cryptoInfo . PaymentUrls ) ;
2023-07-19 18:47:32 +09:00
dto . PaymentSubtotals . Add ( paymentId . ToString ( ) , accounting . ToSmallestUnit ( subtotalPrice ) ) ;
dto . PaymentTotals . Add ( paymentId . ToString ( ) , 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
} ) ;
2018-05-16 10:40:25 +09:00
dto . Addresses . Add ( paymentId . ToString ( ) , address ) ;
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 ;
2022-11-02 10:21:33 +01:00
dto . CheckoutType = CheckoutType ;
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 ) ) ;
dto . Buyer . Add ( new JProperty ( "email" , string . IsNullOrWhiteSpace ( Metadata . BuyerEmail ) ? RefundMail : 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
2018-02-19 15:09:05 +09:00
internal bool Support ( PaymentMethodId paymentMethodId )
2017-12-21 15:52:04 +09:00
{
2019-05-24 22:22:38 +09:00
var rates = GetPaymentMethods ( ) ;
2018-02-19 15:09:05 +09:00
return rates . TryGet ( paymentMethodId ) ! = null ;
2017-12-21 15:52:04 +09:00
}
2019-06-07 13:24:36 +09:00
public PaymentMethod GetPaymentMethod ( PaymentMethodId paymentMethodId )
2017-12-21 15:52:04 +09:00
{
2019-05-24 22:22:38 +09:00
GetPaymentMethods ( ) . TryGetValue ( paymentMethodId , out var data ) ;
2017-12-21 15:52:04 +09:00
return data ;
}
2019-06-07 13:24:36 +09:00
public PaymentMethod GetPaymentMethod ( BTCPayNetworkBase network , PaymentType paymentType )
2017-12-21 15:52:04 +09:00
{
2019-06-07 13:24:36 +09:00
return GetPaymentMethod ( new PaymentMethodId ( network . CryptoCode , paymentType ) ) ;
2017-12-21 15:52:04 +09:00
}
2019-05-24 22:22:38 +09:00
public PaymentMethodDictionary GetPaymentMethods ( )
2017-12-21 15:52:04 +09:00
{
2019-01-05 13:31:05 +09:00
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary ( ) ;
2019-11-15 18:53:20 +09:00
var serializer = new Serializer ( null ) ;
2017-12-21 15:52:04 +09:00
#pragma warning disable CS0618
2018-02-19 15:09:05 +09:00
if ( PaymentMethod ! = null )
2017-12-21 15:52:04 +09:00
{
2018-02-19 15:09:05 +09:00
foreach ( var prop in PaymentMethod . Properties ( ) )
2017-12-21 15:52:04 +09:00
{
2018-02-19 15:09:05 +09:00
var r = serializer . ToObject < PaymentMethod > ( prop . Value . ToString ( ) ) ;
2020-08-09 14:43:13 +02:00
if ( ! PaymentMethodId . TryParse ( prop . Name , out var paymentMethodId ) )
{
continue ;
}
2023-07-19 18:47:32 +09:00
r . Currency = paymentMethodId . CryptoCode ;
2018-02-19 15:09:05 +09:00
r . PaymentType = paymentMethodId . PaymentType . ToString ( ) ;
2017-12-21 15:52:04 +09:00
r . ParentEntity = this ;
2020-07-30 11:36:02 +09:00
if ( Networks ! = null )
{
2023-07-19 18:47:32 +09:00
r . Network = Networks . GetNetwork < BTCPayNetworkBase > ( r . Currency ) ;
2020-07-30 11:36:02 +09:00
if ( r . Network is null )
continue ;
}
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
}
2018-02-19 15:09:05 +09:00
public void SetPaymentMethod ( PaymentMethod paymentMethod )
2018-01-11 17:29:48 +09:00
{
2019-05-24 22:22:38 +09:00
var dict = GetPaymentMethods ( ) ;
2018-02-19 15:09:05 +09:00
dict . AddOrReplace ( paymentMethod ) ;
SetPaymentMethods ( dict ) ;
2018-01-11 17:29:48 +09:00
}
2018-02-19 15:09:05 +09:00
public void SetPaymentMethods ( PaymentMethodDictionary paymentMethods )
2017-12-21 15:52:04 +09:00
{
var obj = new JObject ( ) ;
2019-11-15 18:53:20 +09:00
var serializer = new Serializer ( null ) ;
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
{
2018-02-19 15:09:05 +09:00
var clone = serializer . ToObject < PaymentMethod > ( serializer . ToString ( v ) ) ;
2023-07-19 18:47:32 +09:00
clone . Currency = null ;
2018-02-19 02:38:03 +09:00
clone . PaymentType = null ;
obj . Add ( new JProperty ( v . GetId ( ) . ToString ( ) , JObject . Parse ( serializer . ToString ( clone ) ) ) ) ;
2017-12-21 15:52:04 +09:00
}
2018-02-19 15:09:05 +09:00
PaymentMethod = 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
}
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
/// <summary>
/// Invoice version < 1 were saving metadata directly under the InvoiceEntity
/// object. But in version > 2, the metadata is saved under the InvoiceEntity.Metadata object
/// This method is extracting metadata from the InvoiceEntity of version < 1 invoices and put them in InvoiceEntity.Metadata.
/// </summary>
internal void MigrateLegacyInvoice ( )
{
T TryParseMetadata < T > ( string field ) where T : class
{
if ( AdditionalData . TryGetValue ( field , out var token ) & & token is JObject obj )
{
return obj . ToObject < T > ( ) ;
}
return null ;
}
if ( TryParseMetadata < BuyerInformation > ( "buyerInformation" ) is BuyerInformation buyerInformation & &
TryParseMetadata < ProductInformation > ( "productInformation" ) is ProductInformation productInformation )
{
var wellknown = new InvoiceMetadata ( )
{
BuyerAddress1 = buyerInformation . BuyerAddress1 ,
BuyerAddress2 = buyerInformation . BuyerAddress2 ,
BuyerCity = buyerInformation . BuyerCity ,
BuyerCountry = buyerInformation . BuyerCountry ,
BuyerEmail = buyerInformation . BuyerEmail ,
BuyerName = buyerInformation . BuyerName ,
BuyerPhone = buyerInformation . BuyerPhone ,
BuyerState = buyerInformation . BuyerState ,
BuyerZip = buyerInformation . BuyerZip ,
ItemCode = productInformation . ItemCode ,
ItemDesc = productInformation . ItemDesc ,
Physical = productInformation . Physical ,
TaxIncluded = productInformation . TaxIncluded
} ;
if ( AdditionalData . TryGetValue ( "posData" , out var token ) & &
token is JValue val & &
val . Type = = JTokenType . String )
{
2023-02-25 23:34:49 +09:00
wellknown . PosDataLegacy = val . Value < string > ( ) ;
2020-08-25 14:33:00 +09:00
}
if ( AdditionalData . TryGetValue ( "orderId" , out var token2 ) & &
token2 is JValue val2 & &
val2 . Type = = JTokenType . String )
{
wellknown . OrderId = val2 . Value < string > ( ) ;
}
Metadata = wellknown ;
2021-01-07 22:30:57 +09:00
Currency = productInformation . Currency ? . Trim ( ) . ToUpperInvariant ( ) ;
2020-08-25 14:33:00 +09:00
Price = productInformation . Price ;
}
else
{
throw new InvalidOperationException ( "Not a legacy invoice" ) ;
}
}
2021-08-03 17:03:00 +09:00
public bool IsUnsetTopUp ( )
{
return Type = = InvoiceType . TopUp & & Price = = 0.0 m ;
}
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
{
New ,
Paid ,
Expired ,
Invalid ,
Complete ,
Confirmed
}
public static class InvoiceStatusLegacyExtensions
{
public static InvoiceStatus ToModernStatus ( this InvoiceStatusLegacy legacy )
{
switch ( legacy )
{
case InvoiceStatusLegacy . Complete :
case InvoiceStatusLegacy . Confirmed :
return InvoiceStatus . Settled ;
case InvoiceStatusLegacy . Expired :
return InvoiceStatus . Expired ;
case InvoiceStatusLegacy . Invalid :
return InvoiceStatus . Invalid ;
case InvoiceStatusLegacy . Paid :
return InvoiceStatus . Processing ;
case InvoiceStatusLegacy . New :
return InvoiceStatus . New ;
default :
throw new NotSupportedException ( ) ;
}
}
}
2018-12-10 15:34:48 +09:00
public class InvoiceState
{
2020-11-23 15:57:05 +09:00
static readonly Dictionary < string , InvoiceStatusLegacy > _StringToInvoiceStatus ;
static readonly Dictionary < InvoiceStatusLegacy , string > _InvoiceStatusToString ;
2018-12-10 21:48:28 +09:00
2020-06-28 22:07:48 -05:00
static readonly Dictionary < string , InvoiceExceptionStatus > _StringToExceptionStatus ;
static readonly Dictionary < InvoiceExceptionStatus , string > _ExceptionStatusToString ;
2018-12-10 21:48:28 +09:00
static InvoiceState ( )
{
2020-11-23 15:57:05 +09:00
_StringToInvoiceStatus = new Dictionary < string , InvoiceStatusLegacy > ( ) ;
_StringToInvoiceStatus . Add ( "paid" , InvoiceStatusLegacy . Paid ) ;
_StringToInvoiceStatus . Add ( "expired" , InvoiceStatusLegacy . Expired ) ;
_StringToInvoiceStatus . Add ( "invalid" , InvoiceStatusLegacy . Invalid ) ;
_StringToInvoiceStatus . Add ( "complete" , InvoiceStatusLegacy . Complete ) ;
_StringToInvoiceStatus . Add ( "new" , InvoiceStatusLegacy . New ) ;
_StringToInvoiceStatus . Add ( "confirmed" , InvoiceStatusLegacy . Confirmed ) ;
2018-12-10 21:48:28 +09:00
_InvoiceStatusToString = _StringToInvoiceStatus . ToDictionary ( kv = > kv . Value , kv = > kv . Key ) ;
_StringToExceptionStatus = new Dictionary < string , InvoiceExceptionStatus > ( ) ;
_StringToExceptionStatus . Add ( string . Empty , InvoiceExceptionStatus . None ) ;
_StringToExceptionStatus . Add ( "paidPartial" , InvoiceExceptionStatus . PaidPartial ) ;
_StringToExceptionStatus . Add ( "paidLate" , InvoiceExceptionStatus . PaidLate ) ;
_StringToExceptionStatus . Add ( "paidOver" , InvoiceExceptionStatus . PaidOver ) ;
_StringToExceptionStatus . Add ( "marked" , InvoiceExceptionStatus . Marked ) ;
_ExceptionStatusToString = _StringToExceptionStatus . ToDictionary ( kv = > kv . Value , kv = > kv . Key ) ;
_StringToExceptionStatus . Add ( "false" , InvoiceExceptionStatus . None ) ;
}
public InvoiceState ( string status , string exceptionStatus )
{
Status = _StringToInvoiceStatus [ status ] ;
ExceptionStatus = _StringToExceptionStatus [ exceptionStatus ? ? string . Empty ] ;
}
2020-11-23 15:57:05 +09:00
public InvoiceState ( InvoiceStatusLegacy status , InvoiceExceptionStatus exceptionStatus )
2018-12-10 21:48:28 +09:00
{
Status = status ;
ExceptionStatus = exceptionStatus ;
}
2020-11-23 15:57:05 +09:00
public InvoiceStatusLegacy Status { get ; }
2018-12-10 21:48:28 +09:00
public InvoiceExceptionStatus ExceptionStatus { get ; }
2020-11-23 15:57:05 +09:00
public static string ToString ( InvoiceStatusLegacy status )
2018-12-10 21:48:28 +09:00
{
return _InvoiceStatusToString [ status ] ;
}
public static string ToString ( InvoiceExceptionStatus exceptionStatus )
{
return _ExceptionStatusToString [ exceptionStatus ] ;
}
2018-12-10 15:34:48 +09:00
public bool CanMarkComplete ( )
{
2023-05-31 04:49:01 +02:00
return Status is InvoiceStatusLegacy . New or InvoiceStatusLegacy . Paid or InvoiceStatusLegacy . Expired or InvoiceStatusLegacy . Invalid | |
( Status ! = InvoiceStatusLegacy . Complete & & ExceptionStatus = = InvoiceExceptionStatus . Marked ) ;
2018-12-10 15:34:48 +09:00
}
public bool CanMarkInvalid ( )
{
2023-05-31 04:49:01 +02:00
return Status is InvoiceStatusLegacy . New or InvoiceStatusLegacy . Paid or InvoiceStatusLegacy . Expired | |
2020-11-23 15:57:05 +09:00
( Status ! = InvoiceStatusLegacy . 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 ( )
{
return Status = = InvoiceStatusLegacy . Confirmed | |
Status = = InvoiceStatusLegacy . Complete | |
( Status = = InvoiceStatusLegacy . Expired & &
( ExceptionStatus = = InvoiceExceptionStatus . PaidLate | |
ExceptionStatus = = InvoiceExceptionStatus . PaidOver | |
ExceptionStatus = = InvoiceExceptionStatus . PaidPartial ) ) | |
Status = = InvoiceStatusLegacy . Invalid ;
}
2020-10-23 21:00:23 +09:00
public override int GetHashCode ( )
{
return HashCode . Combine ( Status , ExceptionStatus ) ;
}
public static bool operator = = ( InvoiceState a , InvoiceState b )
{
if ( a is null & & b is null )
return true ;
if ( a is null )
return false ;
return a . Equals ( b ) ;
}
public static bool operator ! = ( InvoiceState a , InvoiceState b )
{
return ! ( a = = b ) ;
}
public bool Equals ( InvoiceState o )
{
if ( o is null )
return false ;
return o . Status = = Status & & o . ExceptionStatus = = ExceptionStatus ;
}
public override bool Equals ( object obj )
{
if ( obj is InvoiceState o )
{
return this . Equals ( o ) ;
}
return false ;
}
2018-12-10 15:34:48 +09:00
public override string ToString ( )
{
2022-01-11 21:49:56 +09:00
return Status . ToModernStatus ( ) . ToString ( ) + ( ExceptionStatus = = InvoiceExceptionStatus . None ? string . Empty : $" ({ToString(ExceptionStatus)})" ) ;
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>
2023-07-19 18:47:32 +09:00
public decimal CryptoPaid { 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>
/// Total amount of network fee to pay to the invoice
/// </summary>
2023-07-19 18:47:32 +09:00
public decimal NetworkFee { get ; set ; }
2018-05-05 16:07:22 +02:00
/// <summary>
2021-08-03 17:03:00 +09:00
/// Total amount of network fee to pay to the invoice
/// </summary>
2023-07-19 18:47:32 +09:00
public decimal NetworkFeeAlreadyPaid { 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
}
2020-11-06 11:09:17 +01:00
public interface IPaymentMethod
{
PaymentMethodId GetId ( ) ;
decimal Rate { get ; set ; }
IPaymentMethodDetails GetPaymentMethodDetails ( ) ;
}
public class PaymentMethod : IPaymentMethod
2017-12-21 15:52:04 +09:00
{
[JsonIgnore]
public InvoiceEntity ParentEntity { get ; set ; }
2018-01-11 17:29:48 +09:00
[JsonIgnore]
2019-05-29 09:43:50 +00:00
public BTCPayNetworkBase Network { get ; set ; }
2017-12-21 15:52:04 +09:00
[JsonProperty(PropertyName = "cryptoCode", DefaultValueHandling = DefaultValueHandling.Ignore)]
2023-07-19 18:47:32 +09:00
public string Currency { get ; set ; }
2018-02-19 02:38:03 +09:00
[JsonProperty(PropertyName = "paymentType", DefaultValueHandling = DefaultValueHandling.Ignore)]
[Obsolete("Use GetId().PaymentType instead")]
public string PaymentType { get ; set ; }
2019-03-17 21:28:47 +09:00
/// <summary>
/// We only use this to pass a singleton asking to the payment handler to prefer payments through TOR, we don't really
/// need to save this information
/// </summary>
[JsonIgnore]
public bool PreferOnion { get ; set ; }
2018-02-19 02:38:03 +09:00
2018-02-19 15:09:05 +09:00
public PaymentMethodId GetId ( )
2018-02-19 02:38:03 +09:00
{
#pragma warning disable CS0618 // Type or member is obsolete
2023-07-19 18:47:32 +09:00
return new PaymentMethodId ( Currency , string . IsNullOrEmpty ( PaymentType ) ? PaymentTypes . BTCLike : PaymentTypes . Parse ( PaymentType ) ) ;
2018-02-19 02:38:03 +09:00
#pragma warning restore CS0618 // Type or member is obsolete
}
2018-02-19 15:09:05 +09:00
public void SetId ( PaymentMethodId id )
2018-02-19 02:38:03 +09:00
{
#pragma warning disable CS0618 // Type or member is obsolete
2023-07-19 18:47:32 +09:00
Currency = id . CryptoCode ;
2018-02-19 02:38:03 +09:00
PaymentType = id . PaymentType . ToString ( ) ;
#pragma warning restore CS0618 // Type or member is obsolete
}
2017-12-21 15:52:04 +09:00
[JsonProperty(PropertyName = "rate")]
public decimal Rate { get ; set ; }
2018-02-19 02:38:03 +09:00
2018-02-19 15:09:05 +09:00
[Obsolete("Use GetPaymentMethodDetails() instead")]
[JsonProperty(PropertyName = "paymentMethod")]
public JObject PaymentMethodDetails { get ; set ; }
public IPaymentMethodDetails GetPaymentMethodDetails ( )
2018-02-19 02:38:03 +09:00
{
#pragma warning disable CS0618 // Type or member is obsolete
// Legacy, old code does not have PaymentMethods
2018-02-19 15:09:05 +09:00
if ( string . IsNullOrEmpty ( PaymentType ) | | PaymentMethodDetails = = null )
2018-02-19 02:38:03 +09:00
{
2023-01-13 09:29:41 +01:00
return new BitcoinLikeOnChainPaymentMethod
2018-02-19 02:38:03 +09:00
{
FeeRate = FeeRate ,
2018-03-13 15:28:39 +09:00
DepositAddress = string . IsNullOrEmpty ( DepositAddress ) ? null : DepositAddress ,
2019-01-07 15:35:18 +09:00
NextNetworkFee = NextNetworkFee
2018-02-19 02:38:03 +09:00
} ;
}
2023-02-25 23:34:49 +09:00
2023-06-23 19:12:11 +09:00
// A bug in previous version of BTCPay Server wasn't properly serializing those fields
if ( PaymentMethodDetails [ "PaymentHash" ] is JObject )
PaymentMethodDetails [ "PaymentHash" ] = null ;
if ( PaymentMethodDetails [ "Preimage" ] is JObject )
PaymentMethodDetails [ "Preimage" ] = null ;
2023-01-13 09:29:41 +01:00
IPaymentMethodDetails details = GetId ( ) . PaymentType . DeserializePaymentMethodDetails ( Network , PaymentMethodDetails . ToString ( ) ) ;
switch ( details )
2018-02-19 02:38:03 +09:00
{
2023-01-13 09:29:41 +01:00
case BitcoinLikeOnChainPaymentMethod btcLike :
2019-01-07 15:35:18 +09:00
btcLike . NextNetworkFee = NextNetworkFee ;
2018-03-13 15:28:39 +09:00
btcLike . DepositAddress = string . IsNullOrEmpty ( DepositAddress ) ? null : DepositAddress ;
2018-02-26 00:48:12 +09:00
btcLike . FeeRate = FeeRate ;
2023-01-13 09:29:41 +01:00
break ;
case LightningLikePaymentMethodDetails lnLike :
// use set properties and fall back to values from payment data
var payments = ParentEntity . GetPayments ( true ) . Where ( paymentEntity = >
paymentEntity . GetPaymentMethodId ( ) = = GetId ( ) ) ;
var payment = payments . Select ( p = > p . GetCryptoPaymentData ( ) as LightningLikePaymentData ) . FirstOrDefault ( ) ;
var paymentHash = payment ? . PaymentHash ! = null & & payment . PaymentHash ! = default ? payment . PaymentHash : null ;
var preimage = payment ? . Preimage ! = null & & payment . Preimage ! = default ? payment . Preimage : null ;
lnLike . PaymentHash = lnLike . PaymentHash ! = null & & lnLike . PaymentHash ! = default ? lnLike . PaymentHash : paymentHash ;
lnLike . Preimage = lnLike . Preimage ! = null & & lnLike . Preimage ! = default ? lnLike . Preimage : preimage ;
break ;
2018-02-19 02:38:03 +09:00
}
2023-01-13 09:29:41 +01:00
return details ;
2018-02-19 02:38:03 +09:00
#pragma warning restore CS0618 // Type or member is obsolete
}
2018-02-19 18:54:21 +09:00
public PaymentMethod SetPaymentMethodDetails ( IPaymentMethodDetails paymentMethod )
2018-02-19 02:38:03 +09:00
{
#pragma warning disable CS0618 // Type or member is obsolete
// Legacy, need to fill the old fields
if ( PaymentType = = null )
PaymentType = paymentMethod . GetPaymentType ( ) . ToString ( ) ;
else if ( PaymentType ! = paymentMethod . GetPaymentType ( ) . ToString ( ) )
throw new InvalidOperationException ( "Invalid payment method affected" ) ;
2018-02-19 11:06:08 +09:00
if ( paymentMethod is Payments . Bitcoin . BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod )
2018-02-19 02:38:03 +09:00
{
2019-01-07 15:35:18 +09:00
NextNetworkFee = bitcoinPaymentMethod . NextNetworkFee ;
2018-02-19 02:38:03 +09:00
FeeRate = bitcoinPaymentMethod . FeeRate ;
2018-03-17 17:49:42 +09:00
DepositAddress = bitcoinPaymentMethod . DepositAddress ;
2018-02-19 02:38:03 +09:00
}
2020-02-19 09:35:23 +01:00
PaymentMethodDetails = JObject . Parse ( paymentMethod . GetPaymentType ( ) . SerializePaymentMethodDetails ( Network , paymentMethod ) ) ;
2018-02-19 02:38:03 +09:00
#pragma warning restore CS0618 // Type or member is obsolete
2018-02-19 18:54:21 +09:00
return this ;
2018-02-19 02:38:03 +09:00
}
2017-12-21 15:52:04 +09:00
[JsonProperty(PropertyName = "feeRate")]
2018-02-19 02:38:03 +09:00
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).FeeRate")]
2017-12-21 15:52:04 +09:00
public FeeRate FeeRate { get ; set ; }
[JsonProperty(PropertyName = "txFee")]
2019-01-07 15:35:18 +09:00
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).NextNetworkFee")]
public Money NextNetworkFee { get ; set ; }
2017-12-21 15:52:04 +09:00
[JsonProperty(PropertyName = "depositAddress")]
2018-02-19 02:38:03 +09:00
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).DepositAddress")]
2017-12-21 15:52:04 +09:00
public string DepositAddress { 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 ;
2020-01-21 14:28:13 +01:00
int precision = Network ? . Divisibility ? ? 8 ;
2018-02-26 00:48:12 +09:00
var accounting = new PaymentMethodAccounting ( ) ;
2023-07-19 18:47:32 +09:00
var thisPaymentMethodPayments = i . GetPayments ( true ) . Where ( p = > GetId ( ) = = p . GetPaymentMethodId ( ) ) . ToList ( ) ;
accounting . TxCount = thisPaymentMethodPayments . Count ;
accounting . TxRequired = accounting . TxCount ;
var grossDue = i . Price + i . PaidFee ;
if ( i . MinimumNetDue > 0.0 m )
2018-01-09 10:54:19 +09:00
{
2023-07-19 18:47:32 +09:00
accounting . TxRequired + + ;
grossDue + = Rate * ( GetPaymentMethodDetails ( ) ? . GetNextNetworkFee ( ) ? ? 0 m ) ;
2018-01-09 10:54:19 +09:00
}
2023-07-19 18:47:32 +09:00
accounting . Divisibility = precision ;
accounting . TotalDue = Coins ( grossDue / Rate , precision ) ;
accounting . Paid = Coins ( i . PaidAmount . Gross / Rate , precision ) ;
accounting . CryptoPaid = Coins ( thisPaymentMethodPayments . Sum ( p = > p . PaidAmount . Gross ) , precision ) ;
// 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 ;
accounting . DueUncapped = Coins ( dueUncapped / Rate , precision ) ;
accounting . Due = Max ( accounting . DueUncapped , 0.0 m ) ;
accounting . NetworkFee = Coins ( ( grossDue - i . Price ) / Rate , precision ) ;
accounting . NetworkFeeAlreadyPaid = Coins ( i . PaidFee / Rate , precision ) ;
accounting . MinimumTotalDue = Max ( Smallest ( precision ) , Coins ( ( grossDue * ( 1.0 m - ( ( decimal ) i . PaymentTolerance / 100.0 m ) ) ) / Rate , precision ) ) ;
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 )
v = MaxCoinValue ;
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
2017-10-27 17:53:04 +09:00
public class PaymentEntity
{
2019-06-07 13:24:36 +09:00
[NotMapped]
[JsonIgnore]
2019-09-21 16:39:44 +02:00
public BTCPayNetworkBase Network { get ; set ; }
2019-01-05 13:31:05 +09:00
public int Version { get ; set ; }
2020-06-28 17:55:27 +09:00
2020-04-05 23:57:43 +09:00
[Obsolete("Use ReceivedTime instead")]
[JsonProperty("receivedTime", DefaultValueHandling = DefaultValueHandling.Ignore)]
2020-04-06 12:23:56 +09:00
// Old invoices were storing the received time in second
2020-04-05 23:57:43 +09:00
public DateTimeOffset ? ReceivedTimeSeconds
2017-10-27 17:53:04 +09:00
{
get ; set ;
}
2020-04-05 23:57:43 +09:00
[Obsolete("Use ReceivedTime instead")]
[JsonProperty("receivedTimeMs", DefaultValueHandling = DefaultValueHandling.Ignore)]
[JsonConverter(typeof(DateTimeMilliJsonConverter))]
2020-04-06 12:23:56 +09:00
// Our RBF detection logic depends on properly ordering payments based on
// received time, so we needed a received time in milli to ensure that
2020-04-06 17:05:03 +09:00
// even if payments are separated by less than a second, they would still be ordered correctly
2020-04-05 23:57:43 +09:00
public DateTimeOffset ? ReceivedTimeMilli
{
get ; set ;
}
[JsonIgnore]
public DateTimeOffset ReceivedTime
{
get
{
#pragma warning disable 618
2020-04-06 17:05:03 +09:00
return ( ReceivedTimeMilli ? ? ReceivedTimeSeconds ) . GetValueOrDefault ( ) ;
2020-04-05 23:57:43 +09:00
#pragma warning restore 618
}
set
{
#pragma warning disable 618
ReceivedTimeMilli = value ;
#pragma warning restore 618
}
}
2019-01-05 13:31:05 +09:00
public decimal NetworkFee { get ; set ; }
2018-02-18 02:19:35 +09:00
[Obsolete("Use ((BitcoinLikePaymentData)GetCryptoPaymentData()).Outpoint")]
2017-10-27 17:53:04 +09:00
public OutPoint Outpoint
{
get ; set ;
}
2017-12-21 15:52:04 +09:00
2018-02-18 02:19:35 +09:00
[Obsolete("Use ((BitcoinLikePaymentData)GetCryptoPaymentData()).Output")]
2017-10-27 17:53:04 +09:00
public TxOut Output
{
get ; set ;
}
2017-12-21 15:52:04 +09:00
2017-11-06 00:31:02 -08:00
public bool Accounted
{
get ; set ;
}
2017-12-21 15:52:04 +09:00
2023-07-19 18:47:32 +09:00
string _Currency ;
[JsonProperty("cryptoCode")]
public string Currency
2017-12-21 15:52:04 +09:00
{
2023-07-19 18:47:32 +09:00
get
{
return _Currency ? ? "BTC" ;
}
set
{
_Currency = value ;
}
2017-12-21 15:52:04 +09:00
}
2018-02-17 01:34:40 +09:00
[Obsolete("Use GetCryptoPaymentData() instead")]
public string CryptoPaymentData { get ; set ; }
2018-02-19 15:09:05 +09:00
[Obsolete("Use GetpaymentMethodId().PaymentType instead")]
2018-02-17 01:34:40 +09:00
public string CryptoPaymentDataType { 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 ; }
2018-02-17 01:34:40 +09:00
2023-07-19 18:47:32 +09:00
public void UpdateAmounts ( )
{
var pd = GetCryptoPaymentData ( ) ;
if ( pd is null )
return ;
var value = pd . GetValue ( ) ;
PaidAmount = new Amounts ( )
{
Currency = Currency ,
Gross = value ,
Net = value - NetworkFee
} ;
InvoicePaidAmount = new Amounts ( )
{
Currency = InvoiceCurrency ,
Gross = PaidAmount . Gross * Rate ,
Net = PaidAmount . Net * Rate
} ;
}
2018-02-18 02:35:02 +09:00
2018-02-17 01:34:40 +09:00
public CryptoPaymentData GetCryptoPaymentData ( )
{
2019-06-04 09:16:18 +09:00
CryptoPaymentData paymentData = null ;
2019-06-04 01:24:15 +09:00
#pragma warning disable CS0618 // Type or member is obsolete
2019-06-04 09:16:18 +09:00
if ( string . IsNullOrEmpty ( CryptoPaymentData ) )
{
// For invoices created when CryptoPaymentDataType was not existing, we just consider that it is a RBFed payment for safety
var bitcoin = new BitcoinLikePaymentData ( ) ;
2019-06-07 13:24:36 +09:00
bitcoin . Network = Network ;
2019-06-04 09:16:18 +09:00
bitcoin . Outpoint = Outpoint ;
bitcoin . Output = Output ;
bitcoin . RBF = true ;
bitcoin . ConfirmationCount = 0 ;
bitcoin . Legacy = true ;
bitcoin . Output = Output ;
bitcoin . Outpoint = Outpoint ;
paymentData = bitcoin ;
}
2019-06-04 01:24:15 +09:00
else
{
2020-08-09 14:43:13 +02:00
var paymentMethodId = GetPaymentMethodId ( ) ;
if ( paymentMethodId is null )
{
return null ;
}
paymentData = paymentMethodId . PaymentType . DeserializePaymentData ( Network , CryptoPaymentData ) ;
if ( paymentData is null )
{
return null ;
}
2020-11-08 23:37:45 -06:00
2019-06-07 13:24:36 +09:00
paymentData . Network = Network ;
2019-06-04 09:16:18 +09:00
if ( paymentData is BitcoinLikePaymentData bitcoin )
2019-06-04 01:24:15 +09:00
{
2019-06-04 09:16:18 +09:00
bitcoin . Output = Output ;
bitcoin . Outpoint = Outpoint ;
2019-06-04 01:24:15 +09:00
}
}
2019-06-04 09:16:18 +09:00
return paymentData ;
2018-02-17 01:34:40 +09:00
}
2018-02-19 18:54:21 +09:00
public PaymentEntity SetCryptoPaymentData ( CryptoPaymentData cryptoPaymentData )
2018-02-17 01:34:40 +09:00
{
#pragma warning disable CS0618
2018-02-19 11:06:08 +09:00
if ( cryptoPaymentData is Payments . Bitcoin . BitcoinLikePaymentData paymentData )
2018-02-17 01:34:40 +09:00
{
2018-02-18 02:19:35 +09:00
// Legacy
Outpoint = paymentData . Outpoint ;
Output = paymentData . Output ;
///
2018-02-17 01:34:40 +09:00
}
2018-02-26 00:48:12 +09:00
CryptoPaymentDataType = cryptoPaymentData . GetPaymentType ( ) . ToString ( ) ;
2020-06-28 17:55:27 +09:00
CryptoPaymentData = GetPaymentMethodId ( ) . PaymentType . SerializePaymentData ( Network , cryptoPaymentData ) ;
2018-02-17 01:34:40 +09:00
#pragma warning restore CS0618
2018-02-19 18:54:21 +09:00
return this ;
2018-02-17 01:34:40 +09:00
}
2017-12-21 15:52:04 +09:00
2018-03-06 16:37:25 -05:00
public PaymentMethodId GetPaymentMethodId ( )
2018-02-19 02:38:03 +09:00
{
#pragma warning disable CS0618 // Type or member is obsolete
2020-08-09 14:43:13 +02:00
PaymentType paymentType ;
if ( string . IsNullOrEmpty ( CryptoPaymentDataType ) )
{
2020-11-08 23:37:45 -06:00
paymentType = BitcoinPaymentType . Instance ;
2020-08-09 14:43:13 +02:00
}
2020-11-08 23:37:45 -06:00
else if ( ! PaymentTypes . TryParse ( CryptoPaymentDataType , out paymentType ) )
2020-08-09 14:43:13 +02:00
{
return null ;
}
2023-07-19 18:47:32 +09:00
return new PaymentMethodId ( Currency ? ? "BTC" , paymentType ) ;
2018-02-19 02:38:03 +09:00
#pragma warning restore CS0618 // Type or member is obsolete
}
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
{
2019-06-07 13:24:36 +09:00
[JsonIgnore]
BTCPayNetworkBase Network { get ; set ; }
2018-02-18 02:19:35 +09:00
/// <summary>
/// Returns an identifier which uniquely identify the payment
/// </summary>
/// <returns>The payment id</returns>
string GetPaymentId ( ) ;
/// <summary>
/// Returns terms which will be indexed and searchable in the search bar of invoice
/// </summary>
/// <returns>The search terms</returns>
string [ ] GetSearchTerms ( ) ;
2018-02-18 02:35:02 +09:00
/// <summary>
/// Get value of what as been paid
/// </summary>
/// <returns>The amount paid</returns>
2018-02-19 18:54:21 +09:00
decimal GetValue ( ) ;
2019-06-07 13:24:36 +09:00
bool PaymentCompleted ( PaymentEntity entity ) ;
bool PaymentConfirmed ( PaymentEntity entity , SpeedPolicy speedPolicy ) ;
2018-02-17 01:34:40 +09:00
2019-06-04 08:59:01 +09:00
PaymentType GetPaymentType ( ) ;
2019-06-07 13:24:36 +09:00
string GetDestination ( ) ;
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
}