예제 #1
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);
        }
예제 #2
0
        private async Task <PaymentMethod> CreatePaymentMethodAsync(IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreBlob storeBlob)
        {
            var rate = await storeBlob.ApplyRateRules(network, _RateProviders.GetRateProvider(network, false)).GetRateAsync(entity.ProductInformation.Currency);

            PaymentMethod paymentMethod = new PaymentMethod();

            paymentMethod.ParentEntity = entity;
            paymentMethod.Network      = network;
            paymentMethod.SetId(supportedPaymentMethod.PaymentId);
            paymentMethod.Rate = rate;
            var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, network);

            if (storeBlob.NetworkFeeDisabled)
            {
                paymentDetails.SetNoTxFee();
            }
            paymentMethod.SetPaymentMethodDetails(paymentDetails);

            // Check if Lightning Max value is exceeded
            if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.LightningLike &&
                storeBlob.LightningMaxValue != null)
            {
                var lightningMaxValue     = storeBlob.LightningMaxValue;
                var lightningMaxValueRate = 0.0m;
                if (lightningMaxValue.Currency == entity.ProductInformation.Currency)
                {
                    lightningMaxValueRate = paymentMethod.Rate;
                }
                else
                {
                    lightningMaxValueRate = await storeBlob.ApplyRateRules(network, _RateProviders.GetRateProvider(network, false)).GetRateAsync(lightningMaxValue.Currency);
                }

                var lightningMaxValueCrypto = Money.Coins(lightningMaxValue.Value / lightningMaxValueRate);
                if (paymentMethod.Calculate().Due > lightningMaxValueCrypto)
                {
                    throw new PaymentMethodUnavailableException("Lightning max value exceeded");
                }
            }
            ///////////////


#pragma warning disable CS0618
            if (paymentMethod.GetId().IsBTCOnChain)
            {
                entity.TxFee          = paymentMethod.TxFee;
                entity.Rate           = paymentMethod.Rate;
                entity.DepositAddress = paymentMethod.DepositAddress;
            }
#pragma warning restore CS0618
            return(paymentMethod);
        }
예제 #3
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);
        }
예제 #4
0
        private async Task <PaymentMethod> CreatePaymentMethodAsync(Dictionary <CurrencyPair, Task <RateResult> > fetchingByCurrencyPair, IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreData store)
        {
            var storeBlob = store.GetStoreBlob();
            var rate      = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.ProductInformation.Currency)];

            if (rate.Value == null)
            {
                return(null);
            }
            PaymentMethod paymentMethod = new PaymentMethod();

            paymentMethod.ParentEntity = entity;
            paymentMethod.Network      = network;
            paymentMethod.SetId(supportedPaymentMethod.PaymentId);
            paymentMethod.Rate = rate.Value.Value;
            var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, store, network);

            if (storeBlob.NetworkFeeDisabled)
            {
                paymentDetails.SetNoTxFee();
            }
            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.Value.HasValue)
                {
                    var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate.Value.Value);
                    if (compare(paymentMethod.Calculate().Due, limitValueCrypto))
                    {
                        throw new PaymentMethodUnavailableException(errorMessage);
                    }
                }
            }
            ///////////////


#pragma warning disable CS0618
            if (paymentMethod.GetId().IsBTCOnChain)
            {
                entity.TxFee          = paymentMethod.TxFee;
                entity.Rate           = paymentMethod.Rate;
                entity.DepositAddress = paymentMethod.DepositAddress;
            }
#pragma warning restore CS0618
            return(paymentMethod);
        }
예제 #5
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.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 criteria = store.GetPaymentMethodCriteria(_NetworkProvider, storeBlob)?.Find(methodCriteria => methodCriteria.PaymentMethod == supportedPaymentMethod.PaymentId);
                if (criteria?.Value != null)
                {
                    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);
                        }
                    }
                }

#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.ToString()})", InvoiceEventData.EventSeverity.Error);
            }
            return(null);
        }
예제 #6
0
        internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl)
        {
            var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider)
                                          .Select(c =>
                                                  (Handler: (IPaymentMethodHandler)_ServiceProvider.GetService(typeof(IPaymentMethodHandler <>).MakeGenericType(c.GetType())),
                                                   SupportedPaymentMethod: c,
                                                   Network: _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode),
                                                   IsAvailable: Task.FromResult(false)))
                                          .Where(c => c.Network != null)
                                          .Select(c =>
            {
                c.IsAvailable = c.Handler.IsAvailable(c.SupportedPaymentMethod, c.Network);
                return(c);
            })
                                          .ToList();

            foreach (var supportedPaymentMethod in supportedPaymentMethods.ToList())
            {
                if (!await supportedPaymentMethod.IsAvailable)
                {
                    supportedPaymentMethods.Remove(supportedPaymentMethod);
                }
            }
            if (supportedPaymentMethods.Count == 0)
            {
                throw new BitpayHttpException(400, "No derivation strategy are available now for this store");
            }
            var entity = new InvoiceEntity
            {
                InvoiceTime = DateTimeOffset.UtcNow
            };

            entity.SetSupportedPaymentMethods(supportedPaymentMethods.Select(s => s.SupportedPaymentMethod));

            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.BuyerInformation      = Map <Invoice, BuyerInformation>(invoice);
            //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;
            entity.Status             = "new";
            entity.SpeedPolicy        = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);

            var methods = supportedPaymentMethods
                          .Select(async o =>
            {
                var rate = await storeBlob.ApplyRateRules(o.Network, _RateProviders.GetRateProvider(o.Network, false)).GetRateAsync(invoice.Currency);
                PaymentMethod paymentMethod = new PaymentMethod();
                paymentMethod.ParentEntity  = entity;
                paymentMethod.SetId(o.SupportedPaymentMethod.PaymentId);
                paymentMethod.Rate = rate;
                var paymentDetails = await o.Handler.CreatePaymentMethodDetails(o.SupportedPaymentMethod, paymentMethod, o.Network);
                if (storeBlob.NetworkFeeDisabled)
                {
                    paymentDetails.SetNoTxFee();
                }
                paymentMethod.SetPaymentMethodDetails(paymentDetails);
#pragma warning disable CS0618
                if (paymentMethod.GetId().IsBTCOnChain)
                {
                    entity.TxFee          = paymentMethod.TxFee;
                    entity.Rate           = paymentMethod.Rate;
                    entity.DepositAddress = paymentMethod.DepositAddress;
                }
#pragma warning restore CS0618
                return(paymentMethod);
            });
            var paymentMethods = new PaymentMethodDictionary();

            foreach (var method in methods)
            {
                paymentMethods.Add(await method);
            }
#pragma warning disable CS0618
            // Legacy Bitpay clients expect information for BTC information, even if the store do not support it
            var legacyBTCisSet = paymentMethods.Any(p => p.GetId().IsBTCOnChain);
            if (!legacyBTCisSet && _NetworkProvider.BTC != null)
            {
                var btc          = _NetworkProvider.BTC;
                var feeProvider  = ((IFeeProviderFactory)_ServiceProvider.GetService(typeof(IFeeProviderFactory))).CreateFeeProvider(btc);
                var rateProvider = storeBlob.ApplyRateRules(btc, _RateProviders.GetRateProvider(btc, false));
                if (feeProvider != null && rateProvider != null)
                {
                    var gettingFee  = feeProvider.GetFeeRateAsync();
                    var gettingRate = rateProvider.GetRateAsync(invoice.Currency);
                    entity.TxFee = GetTxFee(storeBlob, await gettingFee);
                    entity.Rate  = await gettingRate;
                }
#pragma warning restore CS0618
            }

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

            _EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created"));
            var resp = entity.EntityToDTO(_NetworkProvider);
            return(new DataWrapper <InvoiceResponse>(resp)
            {
                Facade = "pos/invoice"
            });
        }