Task <bool> IPaymentMethodHandler.IsAvailable(ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
 {
     if (supportedPaymentMethod is T method)
     {
         return(IsAvailable(method, network));
     }
     return(Task.FromResult(false));
 }
예제 #2
0
 object IPaymentMethodHandler.PreparePayment(ISupportedPaymentMethod supportedPaymentMethod, StoreData store, BTCPayNetwork network)
 {
     if (supportedPaymentMethod is T method)
     {
         return(PreparePayment(method, store, network));
     }
     throw new NotSupportedException("Invalid supportedPaymentMethod");
 }
예제 #3
0
        public Task <IPaymentMethodDetails> CreatePaymentMethodDetails(ISupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod,
                                                                       StoreData store, BTCPayNetworkBase network, object preparePaymentObject)
        {
            if (supportedPaymentMethod is TSupportedPaymentMethod method && network is TBTCPayNetwork correctNetwork)
            {
                return(CreatePaymentMethodDetails(method, paymentMethod, store, correctNetwork, preparePaymentObject));
            }

            throw new NotSupportedException("Invalid supportedPaymentMethod");
        }
예제 #4
0
        public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod)
        {
            if (supportedPaymentMethod is MoneroSupportedPaymentMethod moneroSupportedPaymentMethod)
            {
                return(new
                {
                    moneroSupportedPaymentMethod.AccountIndex,
                });
            }

            return(null);
        }
예제 #5
0
        public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod, bool canModifyStore)
        {
            if (supportedPaymentMethod is ZcashSupportedPaymentMethod ZcashSupportedPaymentMethod)
            {
                return(new
                {
                    ZcashSupportedPaymentMethod.AccountIndex,
                });
            }

            return(null);
        }
예제 #6
0
 public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod)
 {
     if (supportedPaymentMethod is LightningSupportedPaymentMethod lightningSupportedPaymentMethod)
     {
         return new LightningNetworkPaymentMethodBaseData()
                {
                    ConnectionString = lightningSupportedPaymentMethod.GetDisplayableConnectionString()
                }
     }
     ;
     return(null);
 }
예제 #7
0
        public Task <IPaymentMethodDetails> CreatePaymentMethodDetails(InvoiceLogs logs,
                                                                       ISupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod,
                                                                       StoreData store, BTCPayNetworkBase network, object preparePaymentObject,
                                                                       IEnumerable <PaymentMethodId> invoicePaymentMethods)
        {
            if (supportedPaymentMethod is TSupportedPaymentMethod method && network is TBTCPayNetwork correctNetwork)
            {
                return(CreatePaymentMethodDetails(logs, method, paymentMethod, store, correctNetwork, preparePaymentObject, invoicePaymentMethods));
            }

            throw new NotSupportedException("Invalid supportedPaymentMethod");
        }
예제 #8
0
        /// <summary>
        /// Set or remove a new supported payment method for the store
        /// </summary>
        /// <param name="paymentMethodId">The paymentMethodId</param>
        /// <param name="supportedPaymentMethod">The payment method, or null to remove</param>
        public void SetSupportedPaymentMethod(PaymentMethodId paymentMethodId, ISupportedPaymentMethod supportedPaymentMethod)
        {
            if (supportedPaymentMethod != null && paymentMethodId != null && paymentMethodId != supportedPaymentMethod.PaymentId)
            {
                throw new InvalidOperationException("Incoherent arguments, this should never happen");
            }
            if (supportedPaymentMethod == null && paymentMethodId == null)
            {
                throw new ArgumentException($"{nameof(supportedPaymentMethod)} or {nameof(paymentMethodId)} should be specified");
            }
            if (supportedPaymentMethod != null && paymentMethodId == null)
            {
                paymentMethodId = supportedPaymentMethod.PaymentId;
            }

#pragma warning disable CS0618
            JObject strategies = string.IsNullOrEmpty(DerivationStrategies) ? new JObject() : JObject.Parse(DerivationStrategies);
            bool    existing   = false;
            foreach (var strat in strategies.Properties().ToList())
            {
                var stratId = PaymentMethodId.Parse(strat.Name);
                if (stratId.IsBTCOnChain)
                {
                    // Legacy stuff which should go away
                    DerivationStrategy = null;
                }
                if (stratId == paymentMethodId)
                {
                    if (supportedPaymentMethod == null)
                    {
                        strat.Remove();
                    }
                    else
                    {
                        strat.Value = PaymentMethodExtensions.Serialize(supportedPaymentMethod);
                    }
                    existing = true;
                    break;
                }
            }

            if (!existing && supportedPaymentMethod == null && supportedPaymentMethod.PaymentId.IsBTCOnChain)
            {
                DerivationStrategy = null;
            }
            else if (!existing && supportedPaymentMethod != null)
            {
                strategies.Add(new JProperty(supportedPaymentMethod.PaymentId.ToString(), PaymentMethodExtensions.Serialize(supportedPaymentMethod)));
            }
            DerivationStrategies = strategies.ToString();
#pragma warning restore CS0618
        }
예제 #9
0
        public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod)
        {
            if (supportedPaymentMethod is EthereumSupportedPaymentMethod ethereumSupportedPaymentMethod)
            {
                return(new
                {
                    ethereumSupportedPaymentMethod.XPub
                    //no clue what all those properties saved are and don't care.
                });
            }

            return(null);
        }
예제 #10
0
 public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod)
 {
     if (supportedPaymentMethod is DerivationSchemeSettings derivationSchemeSettings)
     {
         return new OnChainPaymentMethodBaseData()
                {
                    DerivationScheme = derivationSchemeSettings.AccountDerivation.ToString(),
                    AccountKeyPath   = derivationSchemeSettings.GetSigningAccountKeySettings().GetRootedKeyPath(),
                    Label            = derivationSchemeSettings.Label
                }
     }
     ;
     return(null);
 }
예제 #11
0
 public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod, bool canModifyStore)
 {
     if (supportedPaymentMethod is LNURLPaySupportedPaymentMethod lightningSupportedPaymentMethod)
     {
         return new LNURLPayPaymentMethodBaseData()
                {
                    UseBech32Scheme           = lightningSupportedPaymentMethod.UseBech32Scheme,
                    EnableForStandardInvoices = lightningSupportedPaymentMethod.EnableForStandardInvoices,
                    LUD12Enabled = lightningSupportedPaymentMethod.LUD12Enabled
                }
     }
     ;
     return(null);
 }
 public static JToken Serialize(ISupportedPaymentMethod factory)
 {
     // Legacy
     if (factory.PaymentId.PaymentType == PaymentTypes.BTCLike)
     {
         return(new JValue(((DerivationStrategy)factory).DerivationStrategyBase.ToString()));
     }
     //////////////
     else
     {
         var str = JsonConvert.SerializeObject(factory);
         return(JObject.Parse(str));
     }
     throw new NotSupportedException();
 }
 public static JToken Serialize(ISupportedPaymentMethod factory)
 {
     // Legacy
     if (factory.PaymentId.PaymentType == PaymentTypes.BTCLike)
     {
         var derivation = (DerivationSchemeSettings)factory;
         var str        = derivation.Network.NBXplorerNetwork.Serializer.ToString(derivation);
         return(JObject.Parse(str));
     }
     //////////////
     else
     {
         var str = JsonConvert.SerializeObject(factory);
         return(JObject.Parse(str));
     }
 }
예제 #14
0
 public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod, bool canModifyStore)
 {
     if (supportedPaymentMethod is LightningSupportedPaymentMethod lightningSupportedPaymentMethod)
     {
         return new LightningNetworkPaymentMethodBaseData()
                {
                    ConnectionString = lightningSupportedPaymentMethod.IsInternalNode
                 ?
                                       lightningSupportedPaymentMethod.GetDisplayableConnectionString()
                 :
                                       canModifyStore
                     ? lightningSupportedPaymentMethod.GetDisplayableConnectionString()
                     :
                                       "*NEED CanModifyStoreSettings PERMISSION TO VIEW*"
                }
     }
     ;
     return(null);
 }
예제 #15
0
        /// <summary>
        /// Set or remove a new supported payment method for the store
        /// </summary>
        /// <param name="paymentMethodId">The paymentMethodId</param>
        /// <param name="supportedPaymentMethod">The payment method, or null to remove</param>
        public void SetSupportedPaymentMethod(ISupportedPaymentMethod supportedPaymentMethod)
        {
#pragma warning disable CS0618
            JObject strategies = string.IsNullOrEmpty(DerivationStrategies) ? new JObject() : JObject.Parse(DerivationStrategies);
            bool    existing   = false;
            foreach (var strat in strategies.Properties().ToList())
            {
                var stratId = PaymentMethodId.Parse(strat.Name);
                if (stratId.IsBTCOnChain)
                {
                    // Legacy stuff which should go away
                    DerivationStrategy = null;
                }
                if (stratId == supportedPaymentMethod.PaymentId)
                {
                    if (supportedPaymentMethod == null)
                    {
                        strat.Remove();
                    }
                    else
                    {
                        strat.Value = PaymentMethodExtensions.Serialize(supportedPaymentMethod);
                    }
                    existing = true;
                    break;
                }
            }

            if (!existing && supportedPaymentMethod == null && supportedPaymentMethod.PaymentId.IsBTCOnChain)
            {
                DerivationStrategy = null;
            }
            else if (!existing && supportedPaymentMethod != null)
            {
                strategies.Add(new JProperty(supportedPaymentMethod.PaymentId.ToString(), PaymentMethodExtensions.Serialize(supportedPaymentMethod)));
            }
            DerivationStrategies = strategies.ToString();
#pragma warning restore CS0618
        }
예제 #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);
        }
예제 #17
0
        protected override async Task Process(ISupportedPaymentMethod paymentMethod, PayoutData[] payouts)
        {
            var storePaymentMethod = paymentMethod as DerivationSchemeSettings;

            if (storePaymentMethod?.IsHotWallet is not true)
            {
                Logs.PayServer.LogInformation($"Wallet is not a hot wallet.");
                return;
            }

            if (!_explorerClientProvider.IsAvailable(PaymentMethodId.CryptoCode))
            {
                Logs.PayServer.LogInformation($"{paymentMethod.PaymentId.CryptoCode} node is not available");
                return;
            }
            var explorerClient  = _explorerClientProvider.GetExplorerClient(PaymentMethodId.CryptoCode);
            var paymentMethodId = PaymentMethodId.Parse(PaymentMethodId.CryptoCode);
            var network         = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(paymentMethodId.CryptoCode);

            var extKeyStr = await explorerClient.GetMetadataAsync <string>(
                storePaymentMethod.AccountDerivation,
                WellknownMetadataKeys.AccountHDKey);

            if (extKeyStr == null)
            {
                Logs.PayServer.LogInformation($"Wallet keys not found.");
                return;
            }

            var wallet = _btcPayWalletProvider.GetWallet(PaymentMethodId.CryptoCode);

            var reccoins = (await wallet.GetUnspentCoins(storePaymentMethod.AccountDerivation)).ToArray();
            var coins    = reccoins.Select(coin => coin.Coin).ToArray();

            var         accountKey    = ExtKey.Parse(extKeyStr, network.NBitcoinNetwork);
            var         keys          = reccoins.Select(coin => accountKey.Derive(coin.KeyPath).PrivateKey).ToArray();
            Transaction workingTx     = null;
            decimal?    failedAmount  = null;
            var         changeAddress = await explorerClient.GetUnusedAsync(
                storePaymentMethod.AccountDerivation, DerivationFeature.Change, 0, true);

            var feeRate = await explorerClient.GetFeeRateAsync(1, new FeeRate(1m));

            var transfersProcessing = new List <PayoutData>();

            foreach (var transferRequest in payouts)
            {
                var blob = transferRequest.GetBlob(_btcPayNetworkJsonSerializerSettings);
                if (failedAmount.HasValue && blob.CryptoAmount >= failedAmount)
                {
                    continue;
                }

                var claimDestination =
                    await _bitcoinLikePayoutHandler.ParseClaimDestination(paymentMethodId, blob.Destination);

                if (!string.IsNullOrEmpty(claimDestination.error))
                {
                    Logs.PayServer.LogInformation($"Could not process payout {transferRequest.Id} because {claimDestination.error}.");
                    continue;
                }

                var bitcoinClaimDestination = (IBitcoinLikeClaimDestination)claimDestination.destination;
                var txBuilder = network.NBitcoinNetwork.CreateTransactionBuilder()
                                .AddCoins(coins)
                                .AddKeys(keys);

                if (workingTx is not null)
                {
                    foreach (var txout in workingTx.Outputs.Where(txout =>
                                                                  !txout.IsTo(changeAddress.Address)))
                    {
                        txBuilder.Send(txout.ScriptPubKey, txout.Value);
                    }
                }

                txBuilder.Send(bitcoinClaimDestination.Address,
                               new Money(blob.CryptoAmount.Value, MoneyUnit.BTC));

                try
                {
                    txBuilder.SetChange(changeAddress.Address);
                    txBuilder.SendEstimatedFees(feeRate.FeeRate);
                    workingTx = txBuilder.BuildTransaction(true);
                    transfersProcessing.Add(transferRequest);
                }
                catch (NotEnoughFundsException e)
                {
                    Logs.PayServer.LogInformation($"Could not process payout {transferRequest.Id} because of not enough funds. ({e.Missing.GetValue(network)})");
                    failedAmount = blob.CryptoAmount;
                    //keep going, we prioritize withdraws by time but if there is some other we can fit, we should
                }
            }

            if (workingTx is not null)
            {
                try
                {
                    await using var context = _applicationDbContextFactory.CreateContext();
                    var txHash = workingTx.GetHash();
                    Logs.PayServer.LogInformation($"Processing {transfersProcessing.Count} payouts in tx {txHash}");
                    foreach (PayoutData payoutData in transfersProcessing)
                    {
                        context.Attach(payoutData);
                        payoutData.State = PayoutState.InProgress;
                        _bitcoinLikePayoutHandler.SetProofBlob(payoutData,
                                                               new PayoutTransactionOnChainBlob()
                        {
                            Accounted     = true,
                            TransactionId = txHash,
                            Candidates    = new HashSet <uint256>()
                            {
                                txHash
                            }
                        });
                        await context.SaveChangesAsync();
                    }
                    TaskCompletionSource <bool> tcs = new();
                    var cts = new CancellationTokenSource();
                    cts.CancelAfter(TimeSpan.FromSeconds(20));
                    var task = _eventAggregator.WaitNext <NewOnChainTransactionEvent>(
                        e => e.NewTransactionEvent.TransactionData.TransactionHash == txHash,
                        cts.Token);
                    var broadcastResult = await explorerClient.BroadcastAsync(workingTx, cts.Token);

                    if (!broadcastResult.Success)
                    {
                        tcs.SetResult(false);
                    }
                    var walletId = new WalletId(_PayoutProcesserSettings.StoreId, PaymentMethodId.CryptoCode);
                    foreach (PayoutData payoutData in transfersProcessing)
                    {
                        _eventAggregator.Publish(new UpdateTransactionLabel(walletId,
                                                                            txHash,
                                                                            UpdateTransactionLabel.PayoutTemplate(payoutData.Id, payoutData.PullPaymentDataId,
                                                                                                                  walletId.ToString())));
                    }
                    await Task.WhenAny(tcs.Task, task);
                }
                catch (OperationCanceledException)
                {
                }
                catch (Exception e)
                {
                    Logs.PayServer.LogError(e, "Could not finalize and broadcast");
                }
            }
        }
예제 #18
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);
        }
예제 #19
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);
        }
예제 #20
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);
        }
예제 #21
0
 public abstract object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod);
예제 #22
0
 protected abstract Task Process(ISupportedPaymentMethod paymentMethod, PayoutData[] payouts);
예제 #23
0
    protected override async Task Process(ISupportedPaymentMethod paymentMethod, PayoutData[] payouts)
    {
        await using var ctx = _applicationDbContextFactory.CreateContext();


        var lightningSupportedPaymentMethod = (LightningSupportedPaymentMethod)paymentMethod;

        if (lightningSupportedPaymentMethod.IsInternalNode &&
            !(await Task.WhenAll((await _storeRepository.GetStoreUsers(_PayoutProcesserSettings.StoreId))
                                 .Where(user => user.Role == StoreRoles.Owner).Select(user => user.Id)
                                 .Select(s => _userService.IsAdminUser(s)))).Any(b => b))
        {
            return;
        }

        var client =
            lightningSupportedPaymentMethod.CreateLightningClient(_network, _options.Value,
                                                                  _lightningClientFactoryService);



        foreach (var payoutData in payouts)
        {
            var blob  = payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings);
            var claim = await _payoutHandler.ParseClaimDestination(PaymentMethodId, blob.Destination);

            try
            {
                switch (claim.destination)
                {
                case LNURLPayClaimDestinaton lnurlPayClaimDestinaton:
                    var endpoint   = LNURL.LNURL.Parse(lnurlPayClaimDestinaton.LNURL, out var tag);
                    var httpClient = _payoutHandler.CreateClient(endpoint);
                    var lnurlInfo  =
                        (LNURLPayRequest)await LNURL.LNURL.FetchInformation(endpoint, "payRequest",
                                                                            httpClient);

                    var lm = new LightMoney(blob.CryptoAmount.Value, LightMoneyUnit.BTC);
                    if (lm > lnurlInfo.MaxSendable || lm < lnurlInfo.MinSendable)
                    {
                        continue;
                    }
                    else
                    {
                        try
                        {
                            var lnurlPayRequestCallbackResponse =
                                await lnurlInfo.SendRequest(lm, _network.NBitcoinNetwork, httpClient);

                            if (await TrypayBolt(client, blob, payoutData,
                                                 lnurlPayRequestCallbackResponse
                                                 .GetPaymentRequest(_network.NBitcoinNetwork)))
                            {
                                ctx.Attach(payoutData);
                                payoutData.State = PayoutState.Completed;
                            }
                        }
                        catch (LNUrlException)
                        {
                            continue;
                        }
                    }

                    break;

                case BoltInvoiceClaimDestination item1:
                    if (await TrypayBolt(client, blob, payoutData, item1.PaymentRequest))
                    {
                        ctx.Attach(payoutData);
                        payoutData.State = PayoutState.Completed;
                    }

                    break;
                }
            }
            catch (Exception e)
            {
                Logs.PayServer.LogError(e, $"Could not process payout {payoutData.Id}");
            }
        }


        await ctx.SaveChangesAsync();
    }
예제 #24
0
 public abstract object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod, bool canModifyStore);
예제 #25
0
 Task <IPaymentMethodDetails> IPaymentMethodHandler.CreatePaymentMethodDetails(ISupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store, BTCPayNetwork network, object preparePaymentObject)
 {
     if (supportedPaymentMethod is T method)
     {
         return(CreatePaymentMethodDetails(method, paymentMethod, store, network, preparePaymentObject));
     }
     throw new NotSupportedException("Invalid supportedPaymentMethod");
 }
예제 #26
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);
        }
 public static void SetSupportedPaymentMethod(this StoreData storeData, ISupportedPaymentMethod supportedPaymentMethod)
 {
     storeData.SetSupportedPaymentMethod(null, supportedPaymentMethod);
 }
예제 #28
0
 public void SetSupportedPaymentMethod(ISupportedPaymentMethod supportedPaymentMethod)
 {
     SetSupportedPaymentMethod(null, supportedPaymentMethod);
 }
예제 #29
0
 Task <IPaymentMethodDetails> IPaymentMethodHandler.CreatePaymentMethodDetails(ISupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, BTCPayNetwork network)
 {
     if (supportedPaymentMethod is T method)
     {
         return(CreatePaymentMethodDetails(method, paymentMethod, network));
     }
     throw new NotSupportedException("Invalid supportedPaymentMethod");
 }
예제 #30
0
    protected override async Task Process(ISupportedPaymentMethod paymentMethod, PayoutData[] payouts)
    {
        await using var ctx = _applicationDbContextFactory.CreateContext();


        var lightningSupportedPaymentMethod = (LightningSupportedPaymentMethod)paymentMethod;

        if (lightningSupportedPaymentMethod.IsInternalNode &&
            !(await Task.WhenAll((await _storeRepository.GetStoreUsers(_PayoutProcesserSettings.StoreId))
                                 .Where(user => user.Role == StoreRoles.Owner).Select(user => user.Id)
                                 .Select(s => _userService.IsAdminUser(s)))).Any(b => b))
        {
            return;
        }

        var client =
            lightningSupportedPaymentMethod.CreateLightningClient(_network, _options.Value,
                                                                  _lightningClientFactoryService);



        foreach (var payoutData in payouts)
        {
            var blob  = payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings);
            var claim = await _payoutHandler.ParseClaimDestination(PaymentMethodId, blob.Destination);

            try
            {
                switch (claim.destination)
                {
                case LNURLPayClaimDestinaton lnurlPayClaimDestinaton:
                    var lnurlResult = await UILightningLikePayoutController.GetInvoiceFromLNURL(payoutData, _payoutHandler, blob,
                                                                                                lnurlPayClaimDestinaton, _network.NBitcoinNetwork);

                    if (lnurlResult.Item2 is not null)
                    {
                        continue;
                    }

                    if (await TrypayBolt(client, blob, payoutData,
                                         lnurlResult.Item1))
                    {
                        ctx.Attach(payoutData);
                        payoutData.State = PayoutState.Completed;
                    }

                    break;

                case BoltInvoiceClaimDestination item1:
                    if (await TrypayBolt(client, blob, payoutData, item1.PaymentRequest))
                    {
                        ctx.Attach(payoutData);
                        payoutData.State = PayoutState.Completed;
                    }

                    break;
                }
            }
            catch (Exception e)
            {
                Logs.PayServer.LogError(e, $"Could not process payout {payoutData.Id}");
            }
        }


        await ctx.SaveChangesAsync();
    }