2021-08-23 08:13:26 +02:00
#nullable enable
2020-06-29 04:44:35 +02:00
using System ;
2017-09-13 08:47:34 +02:00
using System.Collections.Generic ;
2018-08-30 18:34:39 +02:00
using System.Linq ;
2017-09-13 08:47:34 +02:00
using System.Text ;
2019-03-05 09:09:17 +01:00
using System.Threading ;
2017-09-13 08:47:34 +02:00
using System.Threading.Tasks ;
2020-05-23 21:13:18 +02:00
using BTCPayServer.Client.Models ;
2017-09-13 08:47:34 +02:00
using BTCPayServer.Data ;
2019-01-06 10:12:45 +01:00
using BTCPayServer.Events ;
2020-06-24 10:51:00 +02:00
using BTCPayServer.HostedServices ;
2018-08-30 18:34:39 +02:00
using BTCPayServer.Logging ;
using BTCPayServer.Models ;
using BTCPayServer.Payments ;
using BTCPayServer.Rating ;
using BTCPayServer.Security ;
2021-07-27 08:17:56 +02:00
using BTCPayServer.Services ;
2019-02-19 05:04:58 +01:00
using BTCPayServer.Services.Apps ;
2017-10-20 21:06:37 +02:00
using BTCPayServer.Services.Invoices ;
2017-09-15 09:06:57 +02:00
using BTCPayServer.Services.Rates ;
2018-08-30 18:34:39 +02:00
using BTCPayServer.Services.Stores ;
2018-11-06 08:08:42 +01:00
using BTCPayServer.Validation ;
2018-08-30 18:34:39 +02:00
using Microsoft.AspNetCore.Identity ;
using Microsoft.AspNetCore.Mvc ;
using NBitpayClient ;
2020-08-25 07:33:00 +02:00
using BitpayCreateInvoiceRequest = BTCPayServer . Models . BitpayCreateInvoiceRequest ;
2020-05-23 21:13:18 +02:00
using StoreData = BTCPayServer . Data . StoreData ;
2017-09-13 08:47:34 +02:00
namespace BTCPayServer.Controllers
{
2019-10-06 08:47:46 +02:00
[Filters.BitpayAPIConstraint(false)]
2022-01-07 04:32:00 +01:00
public partial class UIInvoiceController : Controller
2017-10-27 10:53:04 +02:00
{
2020-06-29 05:07:48 +02:00
readonly InvoiceRepository _InvoiceRepository ;
readonly RateFetcher _RateProvider ;
readonly StoreRepository _StoreRepository ;
readonly UserManager < ApplicationUser > _UserManager ;
private readonly CurrencyNameTable _CurrencyNameTable ;
readonly EventAggregator _EventAggregator ;
readonly BTCPayNetworkProvider _NetworkProvider ;
2019-05-29 16:33:31 +02:00
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary ;
2020-06-24 10:51:00 +02:00
private readonly ApplicationDbContextFactory _dbContextFactory ;
private readonly PullPaymentHostedService _paymentHostedService ;
2021-07-27 08:17:56 +02:00
private readonly LanguageService _languageService ;
2022-02-10 04:24:28 +01:00
private readonly ExplorerClientProvider _ExplorerClients ;
private readonly UIWalletsController _walletsController ;
2020-11-06 12:42:26 +01:00
2022-01-11 05:15:48 +01:00
public WebhookSender WebhookNotificationManager { get ; }
2020-11-06 12:42:26 +01:00
2022-01-07 04:32:00 +01:00
public UIInvoiceController (
2018-02-20 04:45:04 +01:00
InvoiceRepository invoiceRepository ,
2017-10-27 11:58:43 +02:00
CurrencyNameTable currencyNameTable ,
2017-10-27 10:53:04 +02:00
UserManager < ApplicationUser > userManager ,
2018-08-22 09:53:40 +02:00
RateFetcher rateProvider ,
2017-10-27 10:53:04 +02:00
StoreRepository storeRepository ,
2017-12-17 11:58:55 +01:00
EventAggregator eventAggregator ,
2018-07-12 10:38:21 +02:00
ContentSecurityPolicies csp ,
2019-05-24 08:38:47 +02:00
BTCPayNetworkProvider networkProvider ,
2020-06-24 10:51:00 +02:00
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary ,
ApplicationDbContextFactory dbContextFactory ,
2020-11-06 12:42:26 +01:00
PullPaymentHostedService paymentHostedService ,
2022-01-11 05:15:48 +01:00
WebhookSender webhookNotificationManager ,
2022-02-10 04:24:28 +01:00
LanguageService languageService ,
ExplorerClientProvider explorerClients ,
UIWalletsController walletsController )
2017-10-27 10:53:04 +02:00
{
2017-10-27 11:58:43 +02:00
_CurrencyNameTable = currencyNameTable ? ? throw new ArgumentNullException ( nameof ( currencyNameTable ) ) ;
2017-10-27 10:53:04 +02:00
_StoreRepository = storeRepository ? ? throw new ArgumentNullException ( nameof ( storeRepository ) ) ;
_InvoiceRepository = invoiceRepository ? ? throw new ArgumentNullException ( nameof ( invoiceRepository ) ) ;
2018-05-02 20:32:42 +02:00
_RateProvider = rateProvider ? ? throw new ArgumentNullException ( nameof ( rateProvider ) ) ;
2017-10-27 10:53:04 +02:00
_UserManager = userManager ;
2017-12-17 11:58:55 +01:00
_EventAggregator = eventAggregator ;
2017-12-21 07:52:04 +01:00
_NetworkProvider = networkProvider ;
2019-05-29 16:33:31 +02:00
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary ;
2020-06-24 10:51:00 +02:00
_dbContextFactory = dbContextFactory ;
_paymentHostedService = paymentHostedService ;
2020-11-06 12:42:26 +01:00
WebhookNotificationManager = webhookNotificationManager ;
2021-07-27 08:17:56 +02:00
_languageService = languageService ;
2022-02-10 04:24:28 +01:00
this . _ExplorerClients = explorerClients ;
_walletsController = walletsController ;
2017-10-27 10:53:04 +02:00
}
2017-09-13 08:47:34 +02:00
2017-12-21 07:52:04 +01:00
2020-08-25 07:33:00 +02:00
internal async Task < DataWrapper < InvoiceResponse > > CreateInvoiceCore ( BitpayCreateInvoiceRequest invoice ,
2021-08-23 08:13:26 +02:00
StoreData store , string serverUrl , List < string > ? additionalTags = null ,
2020-07-22 13:58:41 +02:00
CancellationToken cancellationToken = default )
{
var entity = await CreateInvoiceCoreRaw ( invoice , store , serverUrl , additionalTags , cancellationToken ) ;
var resp = entity . EntityToDTO ( ) ;
2020-08-26 07:01:39 +02:00
return new DataWrapper < InvoiceResponse > ( resp ) { Facade = "pos/invoice" } ;
2020-07-22 13:58:41 +02:00
}
2021-08-23 08:13:26 +02:00
internal async Task < InvoiceEntity > CreateInvoiceCoreRaw ( BitpayCreateInvoiceRequest invoice , StoreData store , string serverUrl , List < string > ? additionalTags = null , CancellationToken cancellationToken = default )
2017-10-27 10:53:04 +02:00
{
2017-12-21 07:52:04 +01:00
var storeBlob = store . GetStoreBlob ( ) ;
2020-08-26 07:01:39 +02:00
var entity = _InvoiceRepository . CreateNewInvoice ( ) ;
entity . ExpirationTime = invoice . ExpirationTime is DateTimeOffset v ? v : entity . InvoiceTime + storeBlob . InvoiceExpiration ;
entity . MonitoringExpiration = entity . ExpirationTime + storeBlob . MonitoringExpiration ;
2020-02-25 09:22:39 +01:00
if ( entity . ExpirationTime - TimeSpan . FromSeconds ( 30.0 ) < entity . InvoiceTime )
2020-02-25 09:21:08 +01:00
{
throw new BitpayHttpException ( 400 , "The expirationTime is set too soon" ) ;
}
2020-08-25 07:33:00 +02:00
entity . Metadata . OrderId = invoice . OrderId ;
2020-08-26 07:01:39 +02:00
entity . Metadata . PosData = invoice . PosData ;
2017-10-27 10:53:04 +02:00
entity . ServerUrl = serverUrl ;
2018-01-07 20:14:35 +01:00
entity . FullNotifications = invoice . FullNotifications | | invoice . ExtendedNotifications ;
entity . ExtendedNotifications = invoice . ExtendedNotifications ;
2019-09-04 11:01:26 +02:00
entity . NotificationURLTemplate = invoice . NotificationURL ;
2018-10-12 03:09:13 +02:00
entity . NotificationEmail = invoice . NotificationEmail ;
2019-02-19 03:14:21 +01:00
if ( additionalTags ! = null )
entity . InternalTags . AddRange ( additionalTags ) ;
2020-08-25 07:33:00 +02:00
FillBuyerInfo ( invoice , entity ) ;
2019-01-15 14:12:29 +01:00
2019-02-21 13:58:49 +01:00
var taxIncluded = invoice . TaxIncluded . HasValue ? invoice . TaxIncluded . Value : 0 m ;
2021-07-30 11:46:49 +02:00
var price = invoice . Price ;
2019-04-03 07:38:35 +02:00
2020-08-25 07:33:00 +02:00
entity . Metadata . ItemCode = invoice . ItemCode ;
entity . Metadata . ItemDesc = invoice . ItemDesc ;
entity . Metadata . Physical = invoice . Physical ;
entity . Metadata . TaxIncluded = invoice . TaxIncluded ;
entity . Currency = invoice . Currency ;
2021-08-03 10:03:00 +02:00
if ( price is decimal vv )
{
entity . Price = vv ;
entity . Type = InvoiceType . Standard ;
}
else
{
entity . Price = 0 m ;
entity . Type = InvoiceType . TopUp ;
}
2019-01-15 14:12:29 +01:00
2019-09-02 09:04:41 +02:00
entity . RedirectURLTemplate = invoice . RedirectURL ? ? store . StoreWebsite ;
2019-04-11 11:53:31 +02:00
entity . RedirectAutomatically =
invoice . RedirectAutomatically . GetValueOrDefault ( storeBlob . RedirectAutomatically ) ;
2021-10-27 16:32:56 +02:00
entity . RequiresRefundEmail = invoice . RequiresRefundEmail ;
2017-10-27 10:53:04 +02:00
entity . SpeedPolicy = ParseSpeedPolicy ( invoice . TransactionSpeed , store . SpeedPolicy ) ;
2017-11-12 15:23:21 +01:00
2021-08-23 08:13:26 +02:00
IPaymentFilter ? excludeFilter = null ;
2020-05-24 12:11:26 +02:00
if ( invoice . PaymentCurrencies ? . Any ( ) is true )
{
2020-07-24 08:16:41 +02:00
invoice . SupportedTransactionCurrencies ? ? =
new Dictionary < string , InvoiceSupportedTransactionCurrency > ( ) ;
2020-05-24 12:11:26 +02:00
foreach ( string paymentCurrency in invoice . PaymentCurrencies )
{
invoice . SupportedTransactionCurrencies . TryAdd ( paymentCurrency ,
2020-06-28 10:55:27 +02:00
new InvoiceSupportedTransactionCurrency ( ) { Enabled = true } ) ;
2020-05-24 12:11:26 +02:00
}
}
2019-02-21 11:34:11 +01:00
if ( invoice . SupportedTransactionCurrencies ! = null & & invoice . SupportedTransactionCurrencies . Count ! = 0 )
{
var supportedTransactionCurrencies = invoice . SupportedTransactionCurrencies
. Where ( c = > c . Value . Enabled )
. Select ( c = > PaymentMethodId . TryParse ( c . Key , out var p ) ? p : null )
2020-08-26 07:01:39 +02:00
. Where ( c = > c ! = null )
2019-02-21 11:34:11 +01:00
. ToHashSet ( ) ;
2020-08-26 07:01:39 +02:00
excludeFilter = PaymentFilter . Where ( p = > ! supportedTransactionCurrencies . Contains ( p ) ) ;
}
entity . PaymentTolerance = storeBlob . PaymentTolerance ;
2021-08-23 06:55:06 +02:00
entity . DefaultPaymentMethod = invoice . DefaultPaymentMethod ;
2021-10-27 16:32:56 +02:00
entity . RequiresRefundEmail = invoice . RequiresRefundEmail ;
2021-07-14 16:32:20 +02:00
return await CreateInvoiceCoreRaw ( entity , store , excludeFilter , null , cancellationToken ) ;
2020-08-26 07:01:39 +02:00
}
2021-08-23 08:13:26 +02:00
internal async Task < InvoiceEntity > CreateInvoiceCoreRaw ( CreateInvoiceRequest invoice , StoreData store , string serverUrl , List < string > ? additionalTags = null , CancellationToken cancellationToken = default )
2020-08-26 07:01:39 +02:00
{
var storeBlob = store . GetStoreBlob ( ) ;
var entity = _InvoiceRepository . CreateNewInvoice ( ) ;
2021-10-29 14:50:18 +02:00
entity . ServerUrl = serverUrl ;
2020-08-26 07:01:39 +02:00
entity . ExpirationTime = entity . InvoiceTime + ( invoice . Checkout . Expiration ? ? storeBlob . InvoiceExpiration ) ;
entity . MonitoringExpiration = entity . ExpirationTime + ( invoice . Checkout . Monitoring ? ? storeBlob . MonitoringExpiration ) ;
if ( invoice . Metadata ! = null )
entity . Metadata = InvoiceMetadata . FromJObject ( invoice . Metadata ) ;
invoice . Checkout ? ? = new CreateInvoiceRequest . CheckoutOptions ( ) ;
entity . Currency = invoice . Currency ;
2021-08-03 10:03:00 +02:00
if ( invoice . Amount is decimal v )
{
entity . Price = v ;
entity . Type = InvoiceType . Standard ;
}
else
{
entity . Price = 0.0 m ;
entity . Type = InvoiceType . TopUp ;
}
2020-08-26 07:01:39 +02:00
entity . SpeedPolicy = invoice . Checkout . SpeedPolicy ? ? store . SpeedPolicy ;
2020-12-10 15:34:50 +01:00
entity . DefaultLanguage = invoice . Checkout . DefaultLanguage ;
2021-08-30 00:54:54 +02:00
entity . DefaultPaymentMethod = invoice . Checkout . DefaultPaymentMethod ;
2021-03-01 14:34:07 +01:00
entity . RedirectAutomatically = invoice . Checkout . RedirectAutomatically ? ? storeBlob . RedirectAutomatically ;
2021-10-27 16:32:56 +02:00
entity . RequiresRefundEmail = invoice . Checkout . RequiresRefundEmail ;
2021-08-23 08:13:26 +02:00
IPaymentFilter ? excludeFilter = null ;
2020-08-26 07:01:39 +02:00
if ( invoice . Checkout . PaymentMethods ! = null )
{
var supportedTransactionCurrencies = invoice . Checkout . PaymentMethods
. Select ( c = > PaymentMethodId . TryParse ( c , out var p ) ? p : null )
. ToHashSet ( ) ;
excludeFilter = PaymentFilter . Where ( p = > ! supportedTransactionCurrencies . Contains ( p ) ) ;
2019-02-21 11:34:11 +01:00
}
2020-08-26 07:01:39 +02:00
entity . PaymentTolerance = invoice . Checkout . PaymentTolerance ? ? storeBlob . PaymentTolerance ;
2020-12-09 15:20:13 +01:00
entity . RedirectURLTemplate = invoice . Checkout . RedirectURL ? . Trim ( ) ;
2021-10-27 16:32:56 +02:00
entity . RequiresRefundEmail = invoice . Checkout . RequiresRefundEmail ;
2020-09-21 12:33:46 +02:00
if ( additionalTags ! = null )
entity . InternalTags . AddRange ( additionalTags ) ;
2021-07-14 16:32:20 +02:00
return await CreateInvoiceCoreRaw ( entity , store , excludeFilter , invoice . AdditionalSearchTerms , cancellationToken ) ;
2020-08-26 07:01:39 +02:00
}
2019-02-21 11:34:11 +01:00
2021-08-23 08:13:26 +02:00
internal async Task < InvoiceEntity > CreateInvoiceCoreRaw ( InvoiceEntity entity , StoreData store , IPaymentFilter ? invoicePaymentMethodFilter , string [ ] ? additionalSearchTerms = null , CancellationToken cancellationToken = default )
2020-08-26 07:01:39 +02:00
{
InvoiceLogs logs = new InvoiceLogs ( ) ;
2020-08-28 08:49:13 +02:00
logs . Write ( "Creation of invoice starting" , InvoiceEventData . EventSeverity . Info ) ;
2021-10-20 16:17:40 +02:00
var storeBlob = store . GetStoreBlob ( ) ;
if ( string . IsNullOrEmpty ( entity . Currency ) )
entity . Currency = storeBlob . DefaultCurrency ;
entity . Currency = entity . Currency . Trim ( ) . ToUpperInvariant ( ) ;
2021-07-30 11:46:49 +02:00
entity . Price = Math . Max ( 0.0 m , entity . Price ) ;
var currencyInfo = _CurrencyNameTable . GetNumberFormatInfo ( entity . Currency , false ) ;
if ( currencyInfo ! = null )
{
entity . Price = entity . Price . RoundToSignificant ( currencyInfo . CurrencyDecimalDigits ) ;
}
if ( entity . Metadata . TaxIncluded is decimal taxIncluded )
{
if ( currencyInfo ! = null )
{
taxIncluded = taxIncluded . RoundToSignificant ( currencyInfo . CurrencyDecimalDigits ) ;
}
taxIncluded = Math . Max ( 0.0 m , taxIncluded ) ;
taxIncluded = Math . Min ( taxIncluded , entity . Price ) ;
entity . Metadata . TaxIncluded = taxIncluded ;
}
2020-08-26 07:01:39 +02:00
var getAppsTaggingStore = _InvoiceRepository . GetAppsTaggingStore ( store . Id ) ;
if ( entity . Metadata . BuyerEmail ! = null )
{
if ( ! EmailValidator . IsEmail ( entity . Metadata . BuyerEmail ) )
throw new BitpayHttpException ( 400 , "Invalid email" ) ;
entity . RefundMail = entity . Metadata . BuyerEmail ;
}
2020-11-23 07:57:05 +01:00
entity . Status = InvoiceStatusLegacy . New ;
2020-08-26 07:01:39 +02:00
HashSet < CurrencyPair > currencyPairsToFetch = new HashSet < CurrencyPair > ( ) ;
var rules = storeBlob . GetRateRules ( _NetworkProvider ) ;
var excludeFilter = storeBlob . GetExcludedPaymentMethods ( ) ; // Here we can compose filters from other origin with PaymentFilter.Any()
if ( invoicePaymentMethodFilter ! = null )
{
excludeFilter = PaymentFilter . Or ( excludeFilter ,
invoicePaymentMethodFilter ) ;
}
2018-05-02 20:32:42 +02:00
foreach ( var network in store . GetSupportedPaymentMethods ( _NetworkProvider )
2018-07-27 13:37:16 +02:00
. Where ( s = > ! excludeFilter . Match ( s . PaymentId ) )
2019-05-29 11:43:50 +02:00
. Select ( c = > _NetworkProvider . GetNetwork < BTCPayNetworkBase > ( c . PaymentId . CryptoCode ) )
2018-05-02 20:32:42 +02:00
. Where ( c = > c ! = null ) )
{
2020-08-26 07:01:39 +02:00
currencyPairsToFetch . Add ( new CurrencyPair ( network . CryptoCode , entity . Currency ) ) ;
2020-12-29 09:58:35 +01:00
foreach ( var paymentMethodCriteria in storeBlob . PaymentMethodCriteria )
2020-09-15 11:09:09 +02:00
{
if ( paymentMethodCriteria . Value ! = null )
{
currencyPairsToFetch . Add ( new CurrencyPair ( network . CryptoCode , paymentMethodCriteria . Value . Currency ) ) ;
2020-11-09 06:37:45 +01:00
}
2020-09-15 11:09:09 +02:00
}
2018-05-02 20:32:42 +02:00
}
var rateRules = storeBlob . GetRateRules ( _NetworkProvider ) ;
2019-03-05 09:09:17 +01:00
var fetchingByCurrencyPair = _RateProvider . FetchRates ( currencyPairsToFetch , rateRules , cancellationToken ) ;
2018-07-24 05:19:43 +02:00
var fetchingAll = WhenAllFetched ( logs , fetchingByCurrencyPair ) ;
2020-03-10 08:33:50 +01:00
2020-08-26 07:01:39 +02:00
List < ISupportedPaymentMethod > supported = new List < ISupportedPaymentMethod > ( ) ;
var paymentMethods = new PaymentMethodDictionary ( ) ;
2021-09-06 17:23:41 +02:00
bool noNeedForMethods = entity . Type ! = InvoiceType . TopUp & & entity . Price = = 0 m ;
2018-03-25 18:57:44 +02:00
2021-09-06 17:23:41 +02:00
if ( ! noNeedForMethods )
2018-03-25 18:57:44 +02:00
{
2021-09-06 17:23:41 +02:00
// This loop ends with .ToList so we are querying all payment methods at once
// instead of sequentially to improve response time
foreach ( var o in store . GetSupportedPaymentMethods ( _NetworkProvider )
. Where ( s = > ! excludeFilter . Match ( s . PaymentId ) & &
_paymentMethodHandlerDictionary . Support ( s . PaymentId ) )
. Select ( c = >
( Handler : _paymentMethodHandlerDictionary [ c . PaymentId ] ,
SupportedPaymentMethod : c ,
Network : _NetworkProvider . GetNetwork < BTCPayNetworkBase > ( c . PaymentId . CryptoCode ) ) )
. Where ( c = > c . Network ! = null )
. Select ( o = >
( SupportedPaymentMethod : o . SupportedPaymentMethod ,
PaymentMethod : CreatePaymentMethodAsync ( fetchingByCurrencyPair , o . Handler ,
o . SupportedPaymentMethod , o . Network , entity , store , logs ) ) )
. ToList ( ) )
2018-03-28 15:37:01 +02:00
{
2021-09-06 17:23:41 +02:00
var paymentMethod = await o . PaymentMethod ;
if ( paymentMethod = = null )
continue ;
supported . Add ( o . SupportedPaymentMethod ) ;
paymentMethods . Add ( paymentMethod ) ;
2018-03-28 15:37:01 +02:00
}
2018-03-25 18:57:44 +02:00
2021-09-06 17:23:41 +02:00
if ( supported . Count = = 0 )
{
StringBuilder errors = new StringBuilder ( ) ;
if ( ! store . GetSupportedPaymentMethods ( _NetworkProvider ) . Any ( ) )
errors . AppendLine (
"Warning: No wallet has been linked to your BTCPay Store. See the following link for more information on how to connect your store and wallet. (https://docs.btcpayserver.org/WalletSetup/)" ) ;
foreach ( var error in logs . ToList ( ) )
{
errors . AppendLine ( error . ToString ( ) ) ;
}
throw new BitpayHttpException ( 400 , errors . ToString ( ) ) ;
}
}
2018-03-25 18:57:44 +02:00
entity . SetSupportedPaymentMethods ( supported ) ;
entity . SetPaymentMethods ( paymentMethods ) ;
2019-02-19 04:48:08 +01:00
foreach ( var app in await getAppsTaggingStore )
{
2019-02-19 05:04:58 +01:00
entity . InternalTags . Add ( AppService . GetAppInternalTag ( app . Id ) ) ;
2019-02-19 04:48:08 +01:00
}
2019-04-03 08:00:09 +02:00
using ( logs . Measure ( "Saving invoice" ) )
{
2021-07-14 16:32:20 +02:00
entity = await _InvoiceRepository . CreateInvoiceAsync ( store . Id , entity , additionalSearchTerms ) ;
2019-04-03 08:00:09 +02:00
}
2019-04-03 07:38:35 +02:00
_ = Task . Run ( async ( ) = >
{
try
{
await fetchingAll ;
}
catch ( AggregateException ex )
{
2020-08-28 08:49:13 +02:00
ex . Handle ( e = > { logs . Write ( $"Error while fetching rates {ex}" , InvoiceEventData . EventSeverity . Error ) ; return true ; } ) ;
2019-04-03 07:38:35 +02:00
}
await _InvoiceRepository . AddInvoiceLogs ( entity . Id , logs ) ;
} ) ;
2020-07-24 09:40:37 +02:00
_EventAggregator . Publish ( new Events . InvoiceEvent ( entity , InvoiceEvent . Created ) ) ;
2020-07-22 13:58:41 +02:00
return entity ;
2017-10-27 10:53:04 +02:00
}
2017-09-13 08:47:34 +02:00
2018-07-24 05:19:43 +02:00
private Task WhenAllFetched ( InvoiceLogs logs , Dictionary < CurrencyPair , Task < RateResult > > fetchingByCurrencyPair )
2018-03-28 15:37:01 +02:00
{
2018-07-24 05:19:43 +02:00
return Task . WhenAll ( fetchingByCurrencyPair . Select ( async pair = >
2018-03-28 16:15:10 +02:00
{
2018-07-24 05:19:43 +02:00
var rateResult = await pair . Value ;
2020-08-28 08:49:13 +02:00
logs . Write ( $"{pair.Key}: The rating rule is {rateResult.Rule}" , InvoiceEventData . EventSeverity . Info ) ;
logs . Write ( $"{pair.Key}: The evaluated rating rule is {rateResult.EvaluatedRule}" , InvoiceEventData . EventSeverity . Info ) ;
2018-07-24 05:19:43 +02:00
if ( rateResult . Errors . Count ! = 0 )
{
var allRateRuleErrors = string . Join ( ", " , rateResult . Errors . ToArray ( ) ) ;
2020-08-28 08:49:13 +02:00
logs . Write ( $"{pair.Key}: Rate rule error ({allRateRuleErrors})" , InvoiceEventData . EventSeverity . Error ) ;
2018-07-24 05:19:43 +02:00
}
2018-08-22 17:24:33 +02:00
foreach ( var ex in rateResult . ExchangeExceptions )
2018-07-24 05:19:43 +02:00
{
2020-08-28 08:49:13 +02:00
logs . Write ( $"{pair.Key}: Exception reaching exchange {ex.ExchangeName} ({ex.Exception.Message})" , InvoiceEventData . EventSeverity . Error ) ;
2018-07-24 05:19:43 +02:00
}
} ) . ToArray ( ) ) ;
}
2018-04-03 10:39:28 +02:00
2021-08-23 08:13:26 +02:00
private async Task < PaymentMethod ? > CreatePaymentMethodAsync ( Dictionary < CurrencyPair , Task < RateResult > > fetchingByCurrencyPair ,
2020-11-09 06:37:45 +01:00
IPaymentMethodHandler handler , ISupportedPaymentMethod supportedPaymentMethod , BTCPayNetworkBase network , InvoiceEntity entity ,
StoreData store , InvoiceLogs logs )
2018-07-24 05:19:43 +02:00
{
try
2018-04-03 10:39:28 +02:00
{
2019-06-03 18:06:03 +02:00
var logPrefix = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:" ;
2018-07-24 05:19:43 +02:00
var storeBlob = store . GetStoreBlob ( ) ;
2021-04-07 06:08:42 +02:00
2021-08-23 08:13:26 +02:00
object? preparePayment ;
2021-04-07 06:08:42 +02:00
if ( storeBlob . LazyPaymentMethods )
{
preparePayment = null ;
}
else
{
preparePayment = handler . PreparePayment ( supportedPaymentMethod , store , network ) ;
}
2020-08-25 07:33:00 +02:00
var rate = await fetchingByCurrencyPair [ new CurrencyPair ( network . CryptoCode , entity . Currency ) ] ;
2018-07-27 11:04:41 +02:00
if ( rate . BidAsk = = null )
2018-07-24 05:19:43 +02:00
{
return null ;
}
2020-11-09 06:37:45 +01:00
var paymentMethod = new PaymentMethod
{
ParentEntity = entity ,
Network = network ,
Rate = rate . BidAsk . Bid ,
PreferOnion = Uri . TryCreate ( entity . ServerUrl , UriKind . Absolute , out var u ) & & u . DnsSafeHost . EndsWith ( ".onion" , StringComparison . OrdinalIgnoreCase )
} ;
2018-07-24 05:19:43 +02:00
paymentMethod . SetId ( supportedPaymentMethod . PaymentId ) ;
2019-04-03 07:38:35 +02:00
2019-04-03 08:00:09 +02:00
using ( logs . Measure ( $"{logPrefix} Payment method details creation" ) )
{
2020-03-29 17:28:22 +02:00
var paymentDetails = await handler . CreatePaymentMethodDetails ( logs , supportedPaymentMethod , paymentMethod , store , network , preparePayment ) ;
2019-04-03 08:00:09 +02:00
paymentMethod . SetPaymentMethodDetails ( paymentDetails ) ;
}
2018-07-24 05:19:43 +02:00
2020-12-29 09:58:35 +01:00
var criteria = storeBlob . PaymentMethodCriteria ? . Find ( methodCriteria = > methodCriteria . PaymentMethod = = supportedPaymentMethod . PaymentId ) ;
2021-09-06 04:35:26 +02:00
if ( criteria ? . Value ! = null & & entity . Type ! = InvoiceType . TopUp )
2018-03-28 16:15:10 +02:00
{
2020-09-15 11:09:09 +02:00
var currentRateToCrypto =
await fetchingByCurrencyPair [ new CurrencyPair ( supportedPaymentMethod . PaymentId . CryptoCode , criteria . Value . Currency ) ] ;
if ( currentRateToCrypto ? . BidAsk ! = null )
{
var amount = paymentMethod . Calculate ( ) . Due . GetValue ( network as BTCPayNetwork ) ;
var limitValueCrypto = criteria . Value . Value / currentRateToCrypto . BidAsk . Bid ;
2020-11-09 06:37:45 +01:00
2020-09-15 11:09:09 +02:00
if ( amount < limitValueCrypto & & criteria . Above )
{
logs . Write ( $"{logPrefix} invoice amount below accepted value for payment method" , InvoiceEventData . EventSeverity . Error ) ;
return null ;
}
if ( amount > limitValueCrypto & & ! criteria . Above )
{
logs . Write ( $"{logPrefix} invoice amount above accepted value for payment method" , InvoiceEventData . EventSeverity . Error ) ;
return null ;
}
}
2021-11-04 09:12:17 +01:00
else
{
var suffix = currentRateToCrypto ? . EvaluatedRule is string s ? $" ({s})" : string . Empty ;
logs . Write ( $"{logPrefix} This payment method should be created only if the amount of this invoice is in proper range. However, we are unable to fetch the rate of those limits. {suffix}" , InvoiceEventData . EventSeverity . Warning ) ;
}
2018-03-28 16:15:10 +02:00
}
2018-03-28 15:37:01 +02:00
#pragma warning disable CS0618
2018-07-24 05:19:43 +02:00
if ( paymentMethod . GetId ( ) . IsBTCOnChain )
{
2019-01-07 07:35:18 +01:00
entity . TxFee = paymentMethod . NextNetworkFee ;
2018-07-24 05:19:43 +02:00
entity . Rate = paymentMethod . Rate ;
entity . DepositAddress = paymentMethod . DepositAddress ;
}
#pragma warning restore CS0618
return paymentMethod ;
}
catch ( PaymentMethodUnavailableException ex )
2018-03-28 15:37:01 +02:00
{
2020-08-28 08:49:13 +02:00
logs . Write ( $"{supportedPaymentMethod.PaymentId.CryptoCode}: Payment method unavailable ({ex.Message})" , InvoiceEventData . EventSeverity . Error ) ;
2018-03-28 15:37:01 +02:00
}
2018-07-24 05:19:43 +02:00
catch ( Exception ex )
{
2020-11-09 06:37:45 +01:00
logs . Write ( $"{supportedPaymentMethod.PaymentId.CryptoCode}: Unexpected exception ({ex})" , InvoiceEventData . EventSeverity . Error ) ;
2018-07-24 05:19:43 +02:00
}
return null ;
2018-03-28 15:37:01 +02:00
}
2017-10-27 10:53:04 +02:00
private SpeedPolicy ParseSpeedPolicy ( string transactionSpeed , SpeedPolicy defaultPolicy )
{
if ( transactionSpeed = = null )
return defaultPolicy ;
var mappings = new Dictionary < string , SpeedPolicy > ( ) ;
mappings . Add ( "low" , SpeedPolicy . LowSpeed ) ;
2018-05-11 15:12:45 +02:00
mappings . Add ( "low-medium" , SpeedPolicy . LowMediumSpeed ) ;
2017-10-27 10:53:04 +02:00
mappings . Add ( "medium" , SpeedPolicy . MediumSpeed ) ;
mappings . Add ( "high" , SpeedPolicy . HighSpeed ) ;
if ( ! mappings . TryGetValue ( transactionSpeed , out SpeedPolicy policy ) )
policy = defaultPolicy ;
return policy ;
}
2017-10-23 07:51:21 +02:00
2020-08-25 07:33:00 +02:00
private void FillBuyerInfo ( BitpayCreateInvoiceRequest req , InvoiceEntity invoiceEntity )
2017-10-27 10:53:04 +02:00
{
2020-08-25 07:33:00 +02:00
var buyerInformation = invoiceEntity . Metadata ;
buyerInformation . BuyerAddress1 = req . BuyerAddress1 ;
buyerInformation . BuyerAddress2 = req . BuyerAddress2 ;
buyerInformation . BuyerCity = req . BuyerCity ;
buyerInformation . BuyerCountry = req . BuyerCountry ;
buyerInformation . BuyerEmail = req . BuyerEmail ;
buyerInformation . BuyerName = req . BuyerName ;
buyerInformation . BuyerPhone = req . BuyerPhone ;
buyerInformation . BuyerState = req . BuyerState ;
buyerInformation . BuyerZip = req . BuyerZip ;
var buyer = req . Buyer ;
2017-10-27 10:53:04 +02:00
if ( buyer = = null )
return ;
2020-08-25 07:33:00 +02:00
buyerInformation . BuyerAddress1 ? ? = buyer . Address1 ;
buyerInformation . BuyerAddress2 ? ? = buyer . Address2 ;
buyerInformation . BuyerCity ? ? = buyer . City ;
buyerInformation . BuyerCountry ? ? = buyer . country ;
buyerInformation . BuyerEmail ? ? = buyer . email ;
buyerInformation . BuyerName ? ? = buyer . Name ;
buyerInformation . BuyerPhone ? ? = buyer . phone ;
buyerInformation . BuyerState ? ? = buyer . State ;
buyerInformation . BuyerZip ? ? = buyer . zip ;
2017-10-27 10:53:04 +02:00
}
}
2017-09-13 08:47:34 +02:00
}