Ejemplo n.º 1
0
 public async Task AddInvoiceLogs(string invoiceId, InvoiceLogs logs)
 {
     using (var context = _ContextFactory.CreateContext())
     {
         foreach (var log in logs.ToList())
         {
             context.InvoiceEvents.Add(new InvoiceEventData()
             {
                 InvoiceDataId = invoiceId,
                 Message       = log.Log,
                 Timestamp     = log.Timestamp,
                 UniqueId      = Encoders.Hex.EncodeData(RandomUtils.GetBytes(10))
             });
         }
         await context.SaveChangesAsync().ConfigureAwait(false);
     }
 }
 private Task WhenAllFetched(InvoiceLogs logs, Dictionary <CurrencyPair, Task <RateResult> > fetchingByCurrencyPair)
 {
     return(Task.WhenAll(fetchingByCurrencyPair.Select(async pair =>
     {
         var rateResult = await pair.Value;
         logs.Write($"{pair.Key}: La regla de calificación es {rateResult.Rule}");
         logs.Write($"{pair.Key}: La regla de calificación evaluada es {rateResult.EvaluatedRule}");
         if (rateResult.Errors.Count != 0)
         {
             var allRateRuleErrors = string.Join(", ", rateResult.Errors.ToArray());
             logs.Write($"{pair.Key}: Error de regla de tasa ({allRateRuleErrors})");
         }
         foreach (var ex in rateResult.ExchangeExceptions)
         {
             logs.Write($"{pair.Key}: Excepción alcanzando intercambio {ex.ExchangeName} ({ex.Exception.Message})");
         }
     }).ToArray()));
 }
Ejemplo n.º 3
0
 private Task WhenAllFetched(InvoiceLogs logs, Dictionary <CurrencyPair, Task <RateResult> > fetchingByCurrencyPair)
 {
     return(Task.WhenAll(fetchingByCurrencyPair.Select(async pair =>
     {
         var rateResult = await pair.Value;
         logs.Write($"{pair.Key}: The rating rule is {rateResult.Rule}");
         logs.Write($"{pair.Key}: The evaluated rating rule is {rateResult.EvaluatedRule}");
         if (rateResult.Errors.Count != 0)
         {
             var allRateRuleErrors = string.Join(", ", rateResult.Errors.ToArray());
             logs.Write($"{pair.Key}: Rate rule error ({allRateRuleErrors})");
         }
         foreach (var ex in rateResult.ExchangeExceptions)
         {
             logs.Write($"{pair.Key}: Exception reaching exchange {ex.ExchangeName} ({ex.Exception.Message})");
         }
     }).ToArray()));
 }
Ejemplo n.º 4
0
        public static async Task ActivateInvoicePaymentMethod(this InvoiceRepository invoiceRepository,
                                                              EventAggregator eventAggregator, BTCPayNetworkProvider btcPayNetworkProvider, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
                                                              StoreData store, InvoiceEntity invoice, PaymentMethodId paymentMethodId)
        {
            var eligibleMethodToActivate = invoice.GetPaymentMethod(paymentMethodId);

            if (!eligibleMethodToActivate.GetPaymentMethodDetails().Activated)
            {
                var payHandler       = paymentMethodHandlerDictionary[paymentMethodId];
                var supportPayMethod = invoice.GetSupportedPaymentMethod()
                                       .Single(method => method.PaymentId == paymentMethodId);
                var         paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
                var         network       = btcPayNetworkProvider.GetNetwork(paymentMethodId.CryptoCode);
                var         prepare       = payHandler.PreparePayment(supportPayMethod, store, network);
                InvoiceLogs logs          = new InvoiceLogs();
                try
                {
                    logs.Write($"{paymentMethodId}: Activating", InvoiceEventData.EventSeverity.Info);
                    var newDetails = await
                                     payHandler.CreatePaymentMethodDetails(logs, supportPayMethod, paymentMethod, store, network,
                                                                           prepare);

                    eligibleMethodToActivate.SetPaymentMethodDetails(newDetails);
                    await invoiceRepository.UpdateInvoicePaymentMethod(invoice.Id, eligibleMethodToActivate);

                    eventAggregator.Publish(new InvoicePaymentMethodActivated(paymentMethodId, invoice));
                }
                catch (PaymentMethodUnavailableException ex)
                {
                    logs.Write($"{paymentMethodId}: Payment method unavailable ({ex.Message})", InvoiceEventData.EventSeverity.Error);
                }
                catch (Exception ex)
                {
                    logs.Write($"{paymentMethodId}: Unexpected exception ({ex})", InvoiceEventData.EventSeverity.Error);
                }

                await invoiceRepository.AddInvoiceLogs(invoice.Id, logs);

                eventAggregator.Publish(new InvoiceNeedUpdateEvent(invoice.Id));
            }
        }
 public abstract Task <IPaymentMethodDetails> CreatePaymentMethodDetails(
     InvoiceLogs logs,
     TSupportedPaymentMethod supportedPaymentMethod,
     PaymentMethod paymentMethod, StoreData store, TBTCPayNetwork network, object preparePaymentObject);
Ejemplo n.º 6
0
        private async Task <PaymentMethod?> CreatePaymentMethodAsync(Dictionary <CurrencyPair, Task <RateResult> > fetchingByCurrencyPair,
                                                                     IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetworkBase network, InvoiceEntity entity,
                                                                     StoreData store, InvoiceLogs logs)
        {
            try
            {
                var logPrefix = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
                var storeBlob = store.GetStoreBlob();

                object?preparePayment;
                if (storeBlob.LazyPaymentMethods)
                {
                    preparePayment = null;
                }
                else
                {
                    preparePayment = handler.PreparePayment(supportedPaymentMethod, store, network);
                }
                var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.Currency)];
                if (rate.BidAsk == null)
                {
                    return(null);
                }
                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)
                };
                paymentMethod.SetId(supportedPaymentMethod.PaymentId);

                using (logs.Measure($"{logPrefix} Payment method details creation"))
                {
                    var paymentDetails = await handler.CreatePaymentMethodDetails(logs, supportedPaymentMethod, paymentMethod, store, network, preparePayment);

                    paymentMethod.SetPaymentMethodDetails(paymentDetails);
                }

                var criteria = storeBlob.PaymentMethodCriteria?.Find(methodCriteria => methodCriteria.PaymentMethod == supportedPaymentMethod.PaymentId);
                if (criteria?.Value != null && entity.Type != InvoiceType.TopUp)
                {
                    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;

                        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);
                        }
                    }
                    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);
                    }
                }

#pragma warning disable CS0618
                if (paymentMethod.GetId().IsBTCOnChain)
                {
                    entity.TxFee          = paymentMethod.NextNetworkFee;
                    entity.Rate           = paymentMethod.Rate;
                    entity.DepositAddress = paymentMethod.DepositAddress;
                }
#pragma warning restore CS0618
                return(paymentMethod);
            }
            catch (PaymentMethodUnavailableException ex)
            {
                logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Payment method unavailable ({ex.Message})", InvoiceEventData.EventSeverity.Error);
            }
            catch (Exception ex)
            {
                logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Unexpected exception ({ex})", InvoiceEventData.EventSeverity.Error);
            }
            return(null);
        }
Ejemplo n.º 7
0
        internal async Task <InvoiceEntity> CreateInvoiceCoreRaw(InvoiceEntity entity, StoreData store, IPaymentFilter?invoicePaymentMethodFilter, string[]?additionalSearchTerms = null, CancellationToken cancellationToken = default)
        {
            InvoiceLogs logs = new InvoiceLogs();

            logs.Write("Creation of invoice starting", InvoiceEventData.EventSeverity.Info);
            var storeBlob = store.GetStoreBlob();

            if (string.IsNullOrEmpty(entity.Currency))
            {
                entity.Currency = storeBlob.DefaultCurrency;
            }
            entity.Currency = entity.Currency.Trim().ToUpperInvariant();
            entity.Price    = Math.Max(0.0m, 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.0m, taxIncluded);
                taxIncluded = Math.Min(taxIncluded, entity.Price);
                entity.Metadata.TaxIncluded = taxIncluded;
            }

            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;
            }
            entity.Status = InvoiceStatusLegacy.New;
            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);
            }
            foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider)
                     .Where(s => !excludeFilter.Match(s.PaymentId))
                     .Select(c => _NetworkProvider.GetNetwork <BTCPayNetworkBase>(c.PaymentId.CryptoCode))
                     .Where(c => c != null))
            {
                currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, entity.Currency));
                foreach (var paymentMethodCriteria in storeBlob.PaymentMethodCriteria)
                {
                    if (paymentMethodCriteria.Value != null)
                    {
                        currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, paymentMethodCriteria.Value.Currency));
                    }
                }
            }

            var rateRules = storeBlob.GetRateRules(_NetworkProvider);
            var fetchingByCurrencyPair = _RateProvider.FetchRates(currencyPairsToFetch, rateRules, cancellationToken);
            var fetchingAll            = WhenAllFetched(logs, fetchingByCurrencyPair);

            List <ISupportedPaymentMethod> supported = new List <ISupportedPaymentMethod>();
            var paymentMethods = new PaymentMethodDictionary();

            bool noNeedForMethods = entity.Type != InvoiceType.TopUp && entity.Price == 0m;

            if (!noNeedForMethods)
            {
                // 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())
                {
                    var paymentMethod = await o.PaymentMethod;
                    if (paymentMethod == null)
                    {
                        continue;
                    }
                    supported.Add(o.SupportedPaymentMethod);
                    paymentMethods.Add(paymentMethod);
                }

                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());
                }
            }
            entity.SetSupportedPaymentMethods(supported);
            entity.SetPaymentMethods(paymentMethods);
            foreach (var app in await getAppsTaggingStore)
            {
                entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
            }

            using (logs.Measure("Saving invoice"))
            {
                entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, additionalSearchTerms);
            }
            _ = Task.Run(async() =>
            {
                try
                {
                    await fetchingAll;
                }
                catch (AggregateException ex)
                {
                    ex.Handle(e => { logs.Write($"Error while fetching rates {ex}", InvoiceEventData.EventSeverity.Error); return(true); });
                }
                await _InvoiceRepository.AddInvoiceLogs(entity.Id, logs);
            });
            _EventAggregator.Publish(new Events.InvoiceEvent(entity, InvoiceEvent.Created));
            return(entity);
        }
Ejemplo n.º 8
0
        private async Task <PaymentMethod> CreatePaymentMethodAsync(Dictionary <CurrencyPair, Task <RateResult> > fetchingByCurrencyPair, IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetworkBase network, InvoiceEntity entity, StoreData store, InvoiceLogs logs)
        {
            try
            {
                var logPrefix      = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
                var storeBlob      = store.GetStoreBlob();
                var preparePayment = handler.PreparePayment(supportedPaymentMethod, store, network);
                var rate           = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.ProductInformation.Currency)];
                if (rate.BidAsk == null)
                {
                    return(null);
                }
                PaymentMethod paymentMethod = new PaymentMethod();
                paymentMethod.ParentEntity = entity;
                paymentMethod.Network      = network;
                paymentMethod.SetId(supportedPaymentMethod.PaymentId);
                paymentMethod.Rate        = rate.BidAsk.Bid;
                paymentMethod.PreferOnion = Uri.TryCreate(entity.ServerUrl, UriKind.Absolute, out var u) && u.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase);

                using (logs.Measure($"{logPrefix} Payment method details creation"))
                {
                    var paymentDetails = await handler.CreatePaymentMethodDetails(logs, supportedPaymentMethod, paymentMethod, store, network, preparePayment);

                    paymentMethod.SetPaymentMethodDetails(paymentDetails);
                }

                var errorMessage = await
                                   handler
                                   .IsPaymentMethodAllowedBasedOnInvoiceAmount(storeBlob, fetchingByCurrencyPair,
                                                                               paymentMethod.Calculate().Due, supportedPaymentMethod.PaymentId);

                if (!string.IsNullOrEmpty(errorMessage))
                {
                    logs.Write($"{logPrefix} {errorMessage}");
                    return(null);
                }


#pragma warning disable CS0618
                if (paymentMethod.GetId().IsBTCOnChain)
                {
                    entity.TxFee          = paymentMethod.NextNetworkFee;
                    entity.Rate           = paymentMethod.Rate;
                    entity.DepositAddress = paymentMethod.DepositAddress;
                }
#pragma warning restore CS0618
                return(paymentMethod);
            }
            catch (PaymentMethodUnavailableException ex)
            {
                logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Payment method unavailable ({ex.Message})");
            }
            catch (Exception ex)
            {
                logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Unexpected exception ({ex.ToString()})");
            }
            return(null);
        }
Ejemplo n.º 9
0
        internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl)
        {
            if (!store.HasClaim(Policies.CanCreateInvoice.Key))
            {
                throw new UnauthorizedAccessException();
            }
            InvoiceLogs logs = new InvoiceLogs();

            logs.Write("Creation of invoice starting");
            var entity = new InvoiceEntity
            {
                InvoiceTime = DateTimeOffset.UtcNow
            };

            var storeBlob       = store.GetStoreBlob();
            Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null;

            if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ?
            {
                notificationUri = null;
            }
            EmailAddressAttribute emailValidator = new EmailAddressAttribute();

            entity.ExpirationTime       = entity.InvoiceTime.AddMinutes(storeBlob.InvoiceExpiration);
            entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration);
            entity.OrderId               = invoice.OrderId;
            entity.ServerUrl             = serverUrl;
            entity.FullNotifications     = invoice.FullNotifications || invoice.ExtendedNotifications;
            entity.ExtendedNotifications = invoice.ExtendedNotifications;
            entity.NotificationURL       = notificationUri?.AbsoluteUri;
            entity.NotificationEmail     = invoice.NotificationEmail;
            entity.BuyerInformation      = Map <Invoice, BuyerInformation>(invoice);
            entity.PaymentTolerance      = storeBlob.PaymentTolerance;
            //Another way of passing buyer info to support
            FillBuyerInfo(invoice.Buyer, entity.BuyerInformation);
            if (entity?.BuyerInformation?.BuyerEmail != null)
            {
                if (!EmailValidator.IsEmail(entity.BuyerInformation.BuyerEmail))
                {
                    throw new BitpayHttpException(400, "Invalid email");
                }
                entity.RefundMail = entity.BuyerInformation.BuyerEmail;
            }
            entity.ProductInformation = Map <Invoice, ProductInformation>(invoice);
            entity.RedirectURL        = invoice.RedirectURL ?? store.StoreWebsite;
            if (!Uri.IsWellFormedUriString(entity.RedirectURL, UriKind.Absolute))
            {
                entity.RedirectURL = null;
            }

            entity.Status      = "new";
            entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);

            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()

            foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider)
                     .Where(s => !excludeFilter.Match(s.PaymentId))
                     .Select(c => _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode))
                     .Where(c => c != null))
            {
                currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, invoice.Currency));
                if (storeBlob.LightningMaxValue != null)
                {
                    currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, storeBlob.LightningMaxValue.Currency));
                }
                if (storeBlob.OnChainMinValue != null)
                {
                    currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, storeBlob.OnChainMinValue.Currency));
                }
            }

            var rateRules = storeBlob.GetRateRules(_NetworkProvider);
            var fetchingByCurrencyPair = _RateProvider.FetchRates(currencyPairsToFetch, rateRules);

            var fetchingAll             = WhenAllFetched(logs, fetchingByCurrencyPair);
            var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider)
                                          .Where(s => !excludeFilter.Match(s.PaymentId))
                                          .Select(c =>
                                                  (Handler: (IPaymentMethodHandler)_ServiceProvider.GetService(typeof(IPaymentMethodHandler <>).MakeGenericType(c.GetType())),
                                                   SupportedPaymentMethod: c,
                                                   Network: _NetworkProvider.GetNetwork(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();
            List <ISupportedPaymentMethod> supported = new List <ISupportedPaymentMethod>();
            var paymentMethods = new PaymentMethodDictionary();

            foreach (var o in supportedPaymentMethods)
            {
                var paymentMethod = await o.PaymentMethod;
                if (paymentMethod == null)
                {
                    continue;
                }
                supported.Add(o.SupportedPaymentMethod);
                paymentMethods.Add(paymentMethod);
            }

            if (supported.Count == 0)
            {
                StringBuilder errors = new StringBuilder();
                errors.AppendLine("No payment method available for this store");
                foreach (var error in logs.ToList())
                {
                    errors.AppendLine(error.ToString());
                }
                throw new BitpayHttpException(400, errors.ToString());
            }

            entity.SetSupportedPaymentMethods(supported);
            entity.SetPaymentMethods(paymentMethods);
            entity.PosData = invoice.PosData;
            entity         = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, logs, _NetworkProvider);

            await fetchingAll;

            _EventAggregator.Publish(new Events.InvoiceEvent(entity.EntityToDTO(_NetworkProvider), 1001, "invoice_created"));
            var resp = entity.EntityToDTO(_NetworkProvider);

            return(new DataWrapper <InvoiceResponse>(resp)
            {
                Facade = "pos/invoice"
            });
        }
Ejemplo n.º 10
0
        internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List <string> additionalTags = null, CancellationToken cancellationToken = default)
        {
            if (!store.HasClaim(Policies.CanCreateInvoice.Key))
            {
                throw new UnauthorizedAccessException();
            }
            InvoiceLogs logs = new InvoiceLogs();

            logs.Write("Creation of invoice starting");
            var entity = new InvoiceEntity
            {
                Version     = InvoiceEntity.Lastest_Version,
                InvoiceTime = DateTimeOffset.UtcNow,
                Networks    = _NetworkProvider
            };

            var getAppsTaggingStore = _InvoiceRepository.GetAppsTaggingStore(store.Id);
            var storeBlob           = store.GetStoreBlob();
            EmailAddressAttribute emailValidator = new EmailAddressAttribute();

            entity.ExpirationTime       = entity.InvoiceTime.AddMinutes(storeBlob.InvoiceExpiration);
            entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration);
            entity.OrderId               = invoice.OrderId;
            entity.ServerUrl             = serverUrl;
            entity.FullNotifications     = invoice.FullNotifications || invoice.ExtendedNotifications;
            entity.ExtendedNotifications = invoice.ExtendedNotifications;

            if (invoice.NotificationURL != null &&
                Uri.TryCreate(invoice.NotificationURL, UriKind.Absolute, out var notificationUri) &&
                (notificationUri.Scheme == "http" || notificationUri.Scheme == "https"))
            {
                entity.NotificationURL = notificationUri.AbsoluteUri;
            }
            entity.NotificationEmail = invoice.NotificationEmail;
            entity.BuyerInformation  = Map <CreateInvoiceRequest, BuyerInformation>(invoice);
            entity.PaymentTolerance  = storeBlob.PaymentTolerance;
            if (additionalTags != null)
            {
                entity.InternalTags.AddRange(additionalTags);
            }
            //Another way of passing buyer info to support
            FillBuyerInfo(invoice.Buyer, entity.BuyerInformation);
            if (entity?.BuyerInformation?.BuyerEmail != null)
            {
                if (!EmailValidator.IsEmail(entity.BuyerInformation.BuyerEmail))
                {
                    throw new BitpayHttpException(400, "Invalid email");
                }
                entity.RefundMail = entity.BuyerInformation.BuyerEmail;
            }

            var taxIncluded = invoice.TaxIncluded.HasValue ? invoice.TaxIncluded.Value : 0m;

            var currencyInfo = _CurrencyNameTable.GetNumberFormatInfo(invoice.Currency, false);

            if (currencyInfo != null)
            {
                int divisibility = currencyInfo.CurrencyDecimalDigits;
                invoice.Price       = invoice.Price.RoundToSignificant(ref divisibility);
                divisibility        = currencyInfo.CurrencyDecimalDigits;
                invoice.TaxIncluded = taxIncluded.RoundToSignificant(ref divisibility);
            }
            invoice.Price       = Math.Max(0.0m, invoice.Price);
            invoice.TaxIncluded = Math.Max(0.0m, taxIncluded);
            invoice.TaxIncluded = Math.Min(taxIncluded, invoice.Price);

            entity.ProductInformation = Map <CreateInvoiceRequest, ProductInformation>(invoice);


            entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite;
            if (!Uri.IsWellFormedUriString(entity.RedirectURL, UriKind.Absolute))
            {
                entity.RedirectURL = null;
            }

            entity.RedirectAutomatically =
                invoice.RedirectAutomatically.GetValueOrDefault(storeBlob.RedirectAutomatically);

            entity.Status      = InvoiceStatus.New;
            entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);

            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 (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)
                                                     .ToHashSet();
                excludeFilter = PaymentFilter.Or(excludeFilter,
                                                 PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p)));
            }

            foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider)
                     .Where(s => !excludeFilter.Match(s.PaymentId))
                     .Select(c => _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode))
                     .Where(c => c != null))
            {
                currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, invoice.Currency));
                if (storeBlob.LightningMaxValue != null)
                {
                    currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, storeBlob.LightningMaxValue.Currency));
                }
                if (storeBlob.OnChainMinValue != null)
                {
                    currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, storeBlob.OnChainMinValue.Currency));
                }
            }

            var rateRules = storeBlob.GetRateRules(_NetworkProvider);
            var fetchingByCurrencyPair  = _RateProvider.FetchRates(currencyPairsToFetch, rateRules, cancellationToken);
            var fetchingAll             = WhenAllFetched(logs, fetchingByCurrencyPair);
            var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider)
                                          .Where(s => !excludeFilter.Match(s.PaymentId))
                                          .Select(c =>
                                                  (Handler: (IPaymentMethodHandler)_ServiceProvider.GetService(typeof(IPaymentMethodHandler <>).MakeGenericType(c.GetType())),
                                                   SupportedPaymentMethod: c,
                                                   Network: _NetworkProvider.GetNetwork(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();
            List <ISupportedPaymentMethod> supported = new List <ISupportedPaymentMethod>();
            var paymentMethods = new PaymentMethodDictionary();

            foreach (var o in supportedPaymentMethods)
            {
                var paymentMethod = await o.PaymentMethod;
                if (paymentMethod == null)
                {
                    continue;
                }
                supported.Add(o.SupportedPaymentMethod);
                paymentMethods.Add(paymentMethod);
            }

            if (supported.Count == 0)
            {
                StringBuilder errors = new StringBuilder();
                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/btcpay-basics/gettingstarted#connecting-btcpay-store-to-your-wallet)");
                foreach (var error in logs.ToList())
                {
                    errors.AppendLine(error.ToString());
                }
                throw new BitpayHttpException(400, errors.ToString());
            }

            entity.SetSupportedPaymentMethods(supported);
            entity.SetPaymentMethods(paymentMethods);
            entity.PosData = invoice.PosData;

            foreach (var app in await getAppsTaggingStore)
            {
                entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
            }

            using (logs.Measure("Saving invoice"))
            {
                entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity);
            }
            _ = Task.Run(async() =>
            {
                try
                {
                    await fetchingAll;
                }
                catch (AggregateException ex)
                {
                    ex.Handle(e => { logs.Write($"Error while fetching rates {ex}"); return(true); });
                }
                await _InvoiceRepository.AddInvoiceLogs(entity.Id, logs);
            });
            _EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, InvoiceEvent.Created));
            var resp = entity.EntityToDTO();

            return(new DataWrapper <InvoiceResponse>(resp)
            {
                Facade = "pos/invoice"
            });
        }
        public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(InvoiceLogs logs, MoneroSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod,
                                                                                      StoreData store, MoneroLikeSpecificBtcPayNetwork network, object preparePaymentObject)
        {
            if (!_moneroRpcProvider.IsAvailable(network.CryptoCode))
            {
                throw new PaymentMethodUnavailableException($"Node or wallet not available");
            }
            var invoice = paymentMethod.ParentEntity;

            if (!(preparePaymentObject is Prepare moneroPrepare))
            {
                throw new ArgumentException();
            }
            var feeRatePerKb = await moneroPrepare.GetFeeRate;
            var address      = await moneroPrepare.ReserveAddress(invoice.Id);

            var feeRatePerByte = feeRatePerKb.Fee / 1024;

            return(new MoneroLikeOnChainPaymentMethodDetails()
            {
                NextNetworkFee = MoneroMoney.Convert(feeRatePerByte * 100),
                AccountIndex = supportedPaymentMethod.AccountIndex,
                AddressIndex = address.AddressIndex,
                DepositAddress = address.Address
            });
        }
Ejemplo n.º 12
0
        public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(
            InvoiceLogs logs,
            LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, Data.StoreData store,
            BTCPayNetwork network, object preparePaymentObject)
        {
            if (paymentMethod.ParentEntity.Type == InvoiceType.TopUp)
            {
                throw new PaymentMethodUnavailableException("Lightning Network payment method is not available for top-up invoices");
            }

            if (preparePaymentObject is null)
            {
                return(new LightningLikePaymentMethodDetails()
                {
                    Activated = false
                });
            }
            //direct casting to (BTCPayNetwork) is fixed in other pull requests with better generic interfacing for handlers
            var storeBlob = store.GetStoreBlob();
            var test      = GetNodeInfo(supportedPaymentMethod, network, paymentMethod.PreferOnion);

            var     invoice = paymentMethod.ParentEntity;
            decimal due     = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility);

            try
            {
                due = paymentMethod.Calculate().Due.ToDecimal(MoneyUnit.BTC);
            }
            catch (Exception)
            {
                // ignored
            }
            var client = supportedPaymentMethod.CreateLightningClient(network, Options.Value, _lightningClientFactory);
            var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow;

            if (expiry < TimeSpan.Zero)
            {
                expiry = TimeSpan.FromSeconds(1);
            }

            LightningInvoice?lightningInvoice = null;

            string description = storeBlob.LightningDescriptionTemplate;

            description = description.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
                          .Replace("{ItemDescription}", invoice.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
                          .Replace("{OrderId}", invoice.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
            using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT))
            {
                try
                {
                    var request = new CreateInvoiceParams(new LightMoney(due, LightMoneyUnit.BTC), description, expiry);
                    request.PrivateRouteHints = storeBlob.LightningPrivateRouteHints;
                    lightningInvoice          = await client.CreateInvoice(request, cts.Token);
                }
                catch (OperationCanceledException) when(cts.IsCancellationRequested)
                {
                    throw new PaymentMethodUnavailableException("The lightning node did not reply in a timely manner");
                }
                catch (Exception ex)
                {
                    throw new PaymentMethodUnavailableException($"Impossible to create lightning invoice ({ex.Message})", ex);
                }
            }

            var nodeInfo = await test;

            return(new LightningLikePaymentMethodDetails
            {
                Activated = true,
                BOLT11 = lightningInvoice.BOLT11,
                PaymentHash = BOLT11PaymentRequest.Parse(lightningInvoice.BOLT11, network.NBitcoinNetwork).PaymentHash,
                InvoiceId = lightningInvoice.Id,
                NodeInfo = nodeInfo.First().ToString()
            });
        }
Ejemplo n.º 13
0
        public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(InvoiceLogs logs, ZcashSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod,
                                                                                      StoreData store, ZcashLikeSpecificBtcPayNetwork network, object preparePaymentObject, IEnumerable <PaymentMethodId> invoicePaymentMethods)
        {
            if (preparePaymentObject is null)
            {
                return(new ZcashLikeOnChainPaymentMethodDetails()
                {
                    Activated = false
                });
            }

            if (!_ZcashRpcProvider.IsAvailable(network.CryptoCode))
            {
                throw new PaymentMethodUnavailableException($"Node or wallet not available");
            }
            var invoice = paymentMethod.ParentEntity;

            if (!(preparePaymentObject is Prepare ZcashPrepare))
            {
                throw new ArgumentException();
            }
            var feeRatePerKb = await ZcashPrepare.GetFeeRate;
            var address      = await ZcashPrepare.ReserveAddress(invoice.Id);

            var feeRatePerByte = feeRatePerKb.Fee / 1024;

            return(new ZcashLikeOnChainPaymentMethodDetails()
            {
                NextNetworkFee = ZcashMoney.Convert(feeRatePerByte * 100),
                AccountIndex = supportedPaymentMethod.AccountIndex,
                AddressIndex = address.AddressIndex,
                DepositAddress = address.Address,
                Activated = true
            });
        }
        public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(InvoiceLogs logs,
                                                                                      EthereumSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod,
                                                                                      StoreData store, EthereumBTCPayNetwork network, object preparePaymentObject)
        {
            if (preparePaymentObject is null)
            {
                return(new EthereumLikeOnChainPaymentMethodDetails()
                {
                    Activated = false
                });
            }
            if (!_ethereumService.IsAvailable(network.CryptoCode, out var error))
            {
                throw new PaymentMethodUnavailableException(error ?? $"Not configured yet");
            }
            var invoice = paymentMethod.ParentEntity;

            if (!(preparePaymentObject is Prepare ethPrepare))
            {
                throw new ArgumentException();
            }
            var address = await ethPrepare.ReserveAddress(invoice.Id);

            if (address is null || address.Failed)
            {
                throw new PaymentMethodUnavailableException($"could not generate address");
            }

            return(new EthereumLikeOnChainPaymentMethodDetails()
            {
                DepositAddress = address.Address, Index = address.Index, XPub = address.XPub, Activated = true
            });
        }
        public async Task <NodeInfo[]> GetNodeInfo(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceLogs invoiceLogs, bool?preferOnion = null, bool throws = false)
        {
            if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary))
            {
                throw new PaymentMethodUnavailableException("Full node not available");
            }

            try
            {
                using var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT);
                var client = CreateLightningClient(supportedPaymentMethod, network);
                LightningNodeInformation info;
                try
                {
                    info = await client.GetInfo(cts.Token);
                }
                catch (OperationCanceledException) when(cts.IsCancellationRequested)
                {
                    throw new PaymentMethodUnavailableException("The lightning node did not reply in a timely manner");
                }
                catch (Exception ex)
                {
                    throw new PaymentMethodUnavailableException($"Error while connecting to the API: {ex.Message}" +
                                                                (!string.IsNullOrEmpty(ex.InnerException?.Message) ? $" ({ex.InnerException.Message})" : ""));
                }

                var nodeInfo = preferOnion != null && info.NodeInfoList.Any(i => i.IsTor == preferOnion)
                    ? info.NodeInfoList.Where(i => i.IsTor == preferOnion.Value).ToArray()
                    : info.NodeInfoList.Select(i => i).ToArray();

                // Maybe the user does not have an  easily accessible ln node. Node info should be optional. The UI also supports this.
                // if (!nodeInfo.Any())
                // {
                //     throw new PaymentMethodUnavailableException("No lightning node public address has been configured");
                // }

                var blocksGap = summary.Status.ChainHeight - info.BlockHeight;
                if (blocksGap > 10)
                {
                    throw new PaymentMethodUnavailableException($"The lightning node is not synched ({blocksGap} blocks left)");
                }

                return(nodeInfo);
            }
            catch (Exception e) when(!throws)
            {
                invoiceLogs.Write($"NodeInfo failed to be fetched: {e.Message}", InvoiceEventData.EventSeverity.Error);
            }

            return(Array.Empty <NodeInfo>());
        }
Ejemplo n.º 16
0
        private async Task <PaymentMethod> CreatePaymentMethodAsync(Dictionary <CurrencyPair, Task <RateResult> > fetchingByCurrencyPair, IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreData store, InvoiceLogs logs)
        {
            try
            {
                var logPrefix      = $"{handler.ToPrettyString(supportedPaymentMethod.PaymentId)}:";
                var storeBlob      = store.GetStoreBlob();
                var preparePayment = handler.PreparePayment(supportedPaymentMethod, store, network);
                var rate           = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.ProductInformation.Currency)];
                if (rate.BidAsk == null)
                {
                    return(null);
                }
                PaymentMethod paymentMethod = new PaymentMethod();
                paymentMethod.ParentEntity = entity;
                paymentMethod.Network      = network;
                paymentMethod.SetId(supportedPaymentMethod.PaymentId);
                paymentMethod.Rate        = rate.BidAsk.Bid;
                paymentMethod.PreferOnion = this.Request.IsOnion();

                using (logs.Measure($"{logPrefix} Payment method details creation"))
                {
                    var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, store, network, preparePayment);

                    paymentMethod.SetPaymentMethodDetails(paymentDetails);
                }

                Func <Money, Money, bool> compare    = null;
                CurrencyValue             limitValue = null;
                string errorMessage = null;
                if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.LightningLike &&
                    storeBlob.LightningMaxValue != null)
                {
                    compare      = (a, b) => a > b;
                    limitValue   = storeBlob.LightningMaxValue;
                    errorMessage = "The amount of the invoice is too high to be paid with lightning";
                }
                else if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.BTCLike &&
                         storeBlob.OnChainMinValue != null)
                {
                    compare      = (a, b) => a < b;
                    limitValue   = storeBlob.OnChainMinValue;
                    errorMessage = "The amount of the invoice is too low to be paid on chain";
                }

                if (compare != null)
                {
                    var limitValueRate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, limitValue.Currency)];
                    if (limitValueRate.BidAsk != null)
                    {
                        var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate.BidAsk.Bid);
                        if (compare(paymentMethod.Calculate().Due, limitValueCrypto))
                        {
                            logs.Write($"{logPrefix} {errorMessage}");
                            return(null);
                        }
                    }
                }
                ///////////////


#pragma warning disable CS0618
                if (paymentMethod.GetId().IsBTCOnChain)
                {
                    entity.TxFee          = paymentMethod.NextNetworkFee;
                    entity.Rate           = paymentMethod.Rate;
                    entity.DepositAddress = paymentMethod.DepositAddress;
                }
#pragma warning restore CS0618
                return(paymentMethod);
            }
            catch (PaymentMethodUnavailableException ex)
            {
                logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Payment method unavailable ({ex.Message})");
            }
            catch (Exception ex)
            {
                logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Unexpected exception ({ex.ToString()})");
            }
            return(null);
        }
Ejemplo n.º 17
0
        public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(
            InvoiceLogs logs,
            DerivationSchemeSettings supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store,
            BTCPayNetwork network, object preparePaymentObject)
        {
            if (preparePaymentObject is null)
            {
                return(new BitcoinLikeOnChainPaymentMethod()
                {
                    Activated = false
                });
            }
            if (!_ExplorerProvider.IsAvailable(network))
            {
                throw new PaymentMethodUnavailableException($"Full node not available");
            }
            var prepare       = (Prepare)preparePaymentObject;
            var onchainMethod = new BitcoinLikeOnChainPaymentMethod();
            var blob          = store.GetStoreBlob();

            onchainMethod.Activated = true;
            // TODO: this needs to be refactored to move this logic into BitcoinLikeOnChainPaymentMethod
            // This is likely a constructor code
            onchainMethod.NetworkFeeMode = blob.NetworkFeeMode;
            onchainMethod.FeeRate        = await prepare.GetFeeRate;
            switch (onchainMethod.NetworkFeeMode)
            {
            case NetworkFeeMode.Always:
                onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
                onchainMethod.NextNetworkFee =
                    onchainMethod.NetworkFeeRate.GetFee(100);     // assume price for 100 bytes
                break;

            case NetworkFeeMode.Never:
                onchainMethod.NetworkFeeRate = FeeRate.Zero;
                onchainMethod.NextNetworkFee = Money.Zero;
                break;

            case NetworkFeeMode.MultiplePaymentsOnly:
                onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
                onchainMethod.NextNetworkFee = Money.Zero;
                break;
            }

            var reserved = await prepare.ReserveAddress;

            onchainMethod.DepositAddress = reserved.Address.ToString();
            onchainMethod.KeyPath        = reserved.KeyPath;
            onchainMethod.PayjoinEnabled = blob.PayJoinEnabled &&
                                           supportedPaymentMethod
                                           .AccountDerivation.ScriptPubKeyType() != ScriptPubKeyType.Legacy &&
                                           network.SupportPayJoin;
            if (onchainMethod.PayjoinEnabled)
            {
                var prefix      = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
                var nodeSupport = _dashboard?.Get(network.CryptoCode)?.Status?.BitcoinStatus?.Capabilities
                                  ?.CanSupportTransactionCheck is true;
                onchainMethod.PayjoinEnabled &= supportedPaymentMethod.IsHotWallet && nodeSupport;
                if (!supportedPaymentMethod.IsHotWallet)
                {
                    logs.Write($"{prefix} Payjoin should have been enabled, but your store is not a hotwallet", InvoiceEventData.EventSeverity.Warning);
                }
                if (!nodeSupport)
                {
                    logs.Write($"{prefix} Payjoin should have been enabled, but your version of NBXplorer or full node does not support it.", InvoiceEventData.EventSeverity.Warning);
                }
                if (onchainMethod.PayjoinEnabled)
                {
                    logs.Write($"{prefix} Payjoin is enabled for this invoice.", InvoiceEventData.EventSeverity.Info);
                }
            }

            return(onchainMethod);
        }
Ejemplo n.º 18
0
        public async Task <InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, InvoiceLogs creationLogs, BTCPayNetworkProvider networkProvider)
        {
            List <string> textSearch = new List <string>();

            invoice    = Clone(invoice, null);
            invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
#pragma warning disable CS0618
            invoice.Payments = new List <PaymentEntity>();
#pragma warning restore CS0618
            invoice.StoreId = storeId;
            using (var context = _ContextFactory.CreateContext())
            {
                context.Invoices.Add(new Data.InvoiceData()
                {
                    StoreDataId = storeId,
                    Id          = invoice.Id,
                    Created     = invoice.InvoiceTime,
                    Blob        = ToBytes(invoice, null),
                    OrderId     = invoice.OrderId,
#pragma warning disable CS0618 // Type or member is obsolete
                    Status = invoice.StatusString,
#pragma warning restore CS0618 // Type or member is obsolete
                    ItemCode      = invoice.ProductInformation.ItemCode,
                    CustomerEmail = invoice.RefundMail
                });

                foreach (var paymentMethod in invoice.GetPaymentMethods(networkProvider))
                {
                    if (paymentMethod.Network == null)
                    {
                        throw new InvalidOperationException("CryptoCode unsupported");
                    }
                    var paymentDestination = paymentMethod.GetPaymentMethodDetails().GetPaymentDestination();

                    string address = GetDestination(paymentMethod, paymentMethod.Network.NBitcoinNetwork);
                    context.AddressInvoices.Add(new AddressInvoiceData()
                    {
                        InvoiceDataId = invoice.Id,
                        CreatedTime   = DateTimeOffset.UtcNow,
                    }.Set(address, paymentMethod.GetId()));

                    context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
                    {
                        InvoiceDataId = invoice.Id,
                        Assigned      = DateTimeOffset.UtcNow
                    }.SetAddress(paymentDestination, paymentMethod.GetId().ToString()));
                    textSearch.Add(paymentDestination);
                    textSearch.Add(paymentMethod.Calculate().TotalDue.ToString());
                }
                context.PendingInvoices.Add(new PendingInvoiceData()
                {
                    Id = invoice.Id
                });

                foreach (var log in creationLogs.ToList())
                {
                    context.InvoiceEvents.Add(new InvoiceEventData()
                    {
                        InvoiceDataId = invoice.Id,
                        Message       = log.Log,
                        Timestamp     = log.Timestamp,
                        UniqueId      = Encoders.Hex.EncodeData(RandomUtils.GetBytes(10))
                    });
                }
                await context.SaveChangesAsync().ConfigureAwait(false);
            }

            textSearch.Add(invoice.Id);
            textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
            textSearch.Add(invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture));
            textSearch.Add(invoice.OrderId);
            textSearch.Add(ToString(invoice.BuyerInformation, null));
            textSearch.Add(ToString(invoice.ProductInformation, null));
            textSearch.Add(invoice.StoreId);

            AddToTextSearch(invoice.Id, textSearch.ToArray());
            return(invoice);
        }
        public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(
            InvoiceLogs logs,
            DerivationSchemeSettings supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store,
            BTCPayNetwork network, object preparePaymentObject)
        {
            if (!_ExplorerProvider.IsAvailable(network))
            {
                throw new PaymentMethodUnavailableException($"Full node not available");
            }
            var prepare = (Prepare)preparePaymentObject;

            Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod =
                new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod();
            var blob = store.GetStoreBlob();

            onchainMethod.NetworkFeeMode = blob.NetworkFeeMode;
            onchainMethod.FeeRate        = await prepare.GetFeeRate;
            switch (onchainMethod.NetworkFeeMode)
            {
            case NetworkFeeMode.Always:
                onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
                onchainMethod.NextNetworkFee =
                    onchainMethod.NetworkFeeRate.GetFee(100);     // assume price for 100 bytes
                break;

            case NetworkFeeMode.Never:
                onchainMethod.NetworkFeeRate = FeeRate.Zero;
                onchainMethod.NextNetworkFee = Money.Zero;
                break;

            case NetworkFeeMode.MultiplePaymentsOnly:
                onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
                onchainMethod.NextNetworkFee = Money.Zero;
                break;
            }

            onchainMethod.DepositAddress = (await prepare.ReserveAddress).Address.ToString();
            onchainMethod.PayjoinEnabled = blob.PayJoinEnabled &&
                                           PayjoinClient.SupportedFormats.Contains(supportedPaymentMethod
                                                                                   .AccountDerivation.ScriptPubKeyType()) &&
                                           network.SupportPayJoin;
            if (onchainMethod.PayjoinEnabled)
            {
                var prefix      = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
                var nodeSupport = _dashboard?.Get(network.CryptoCode)?.Status?.BitcoinStatus?.Capabilities
                                  ?.CanSupportTransactionCheck is true;
                onchainMethod.PayjoinEnabled &= supportedPaymentMethod.IsHotWallet && nodeSupport;
                if (!isHotwallet)
                {
                    logs.Write($"{prefix} Payjoin should have been enabled, but your store is not a hotwallet");
                }
                if (!nodeSupport)
                {
                    logs.Write($"{prefix} Payjoin should have been enabled, but your version of NBXplorer or full node does not support it.");
                }
                if (onchainMethod.PayjoinEnabled)
                {
                    logs.Write($"{prefix} Payjoin is enabled for this invoice.");
                }
            }

            return(onchainMethod);
        }