2019-05-29 14:33:31 +00:00
|
|
|
|
using System.Collections.Generic;
|
2018-02-20 12:45:04 +09:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using BTCPayServer.Data;
|
2019-05-29 14:33:31 +00:00
|
|
|
|
using BTCPayServer.Models;
|
|
|
|
|
using BTCPayServer.Models.InvoicingModels;
|
|
|
|
|
using BTCPayServer.Rating;
|
2018-02-20 12:45:04 +09:00
|
|
|
|
using BTCPayServer.Services;
|
|
|
|
|
using BTCPayServer.Services.Invoices;
|
|
|
|
|
using BTCPayServer.Services.Rates;
|
|
|
|
|
using NBitcoin;
|
2019-05-29 14:33:31 +00:00
|
|
|
|
using NBitpayClient;
|
|
|
|
|
using Newtonsoft.Json;
|
2018-02-20 12:45:04 +09:00
|
|
|
|
|
|
|
|
|
namespace BTCPayServer.Payments.Bitcoin
|
|
|
|
|
{
|
2019-05-29 14:33:31 +00:00
|
|
|
|
public class BitcoinLikePaymentHandler : PaymentMethodHandlerBase<DerivationSchemeSettings, BTCPayNetwork>
|
2018-02-20 12:45:04 +09:00
|
|
|
|
{
|
|
|
|
|
ExplorerClientProvider _ExplorerProvider;
|
2019-05-29 14:33:31 +00:00
|
|
|
|
private readonly BTCPayNetworkProvider _networkProvider;
|
2018-02-20 12:45:04 +09:00
|
|
|
|
private IFeeProviderFactory _FeeRateProviderFactory;
|
|
|
|
|
private Services.Wallets.BTCPayWalletProvider _WalletProvider;
|
|
|
|
|
|
|
|
|
|
public BitcoinLikePaymentHandler(ExplorerClientProvider provider,
|
2019-05-29 14:33:31 +00:00
|
|
|
|
BTCPayNetworkProvider networkProvider,
|
|
|
|
|
IFeeProviderFactory feeRateProviderFactory,
|
|
|
|
|
Services.Wallets.BTCPayWalletProvider walletProvider)
|
2018-02-20 12:45:04 +09:00
|
|
|
|
{
|
|
|
|
|
_ExplorerProvider = provider;
|
2019-05-29 14:33:31 +00:00
|
|
|
|
_networkProvider = networkProvider;
|
|
|
|
|
_FeeRateProviderFactory = feeRateProviderFactory;
|
2018-02-20 12:45:04 +09:00
|
|
|
|
_WalletProvider = walletProvider;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-21 13:54:52 +09:00
|
|
|
|
class Prepare
|
|
|
|
|
{
|
|
|
|
|
public Task<FeeRate> GetFeeRate;
|
|
|
|
|
public Task<BitcoinAddress> ReserveAddress;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-29 14:33:31 +00:00
|
|
|
|
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse)
|
|
|
|
|
{
|
|
|
|
|
var paymentMethodId = new PaymentMethodId(model.CryptoCode, PaymentTypes.BTCLike);
|
|
|
|
|
|
|
|
|
|
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
|
|
|
|
var network = _networkProvider.GetNetwork<BTCPayNetwork>(model.CryptoCode);
|
|
|
|
|
model.IsLightning = false;
|
|
|
|
|
model.PaymentMethodName = GetPaymentMethodName(network);
|
|
|
|
|
model.CryptoImage = GetCryptoImage(network);
|
|
|
|
|
model.InvoiceBitcoinUrl = cryptoInfo.PaymentUrls.BIP21;
|
|
|
|
|
model.InvoiceBitcoinUrlQR = cryptoInfo.PaymentUrls.BIP21;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetCryptoImage(PaymentMethodId paymentMethodId)
|
|
|
|
|
{
|
|
|
|
|
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
|
|
|
|
return GetCryptoImage(network);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string GetCryptoImage(BTCPayNetworkBase network)
|
|
|
|
|
{
|
|
|
|
|
return network.CryptoImagePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetPaymentMethodName(PaymentMethodId paymentMethodId)
|
|
|
|
|
{
|
|
|
|
|
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
|
|
|
|
return GetPaymentMethodName(network);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override async Task<string> IsPaymentMethodAllowedBasedOnInvoiceAmount(StoreBlob storeBlob,
|
|
|
|
|
Dictionary<CurrencyPair, Task<RateResult>> rate, Money amount, PaymentMethodId paymentMethodId)
|
|
|
|
|
{
|
|
|
|
|
if (storeBlob.OnChainMinValue == null)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var limitValueRate =
|
|
|
|
|
await rate[new CurrencyPair(paymentMethodId.CryptoCode, storeBlob.OnChainMinValue.Currency)];
|
|
|
|
|
|
|
|
|
|
if (limitValueRate.BidAsk != null)
|
|
|
|
|
{
|
|
|
|
|
var limitValueCrypto = Money.Coins(storeBlob.OnChainMinValue.Value / limitValueRate.BidAsk.Bid);
|
|
|
|
|
|
|
|
|
|
if (amount > limitValueCrypto)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "The amount of the invoice is too low to be paid on chain";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
|
|
|
|
{
|
|
|
|
|
return _networkProvider.GetAll()
|
|
|
|
|
.Select(network => new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override CryptoPaymentData GetCryptoPaymentData(PaymentEntity paymentEntity)
|
|
|
|
|
{
|
|
|
|
|
#pragma warning disable CS0618
|
|
|
|
|
|
|
|
|
|
BitcoinLikePaymentData paymentData;
|
|
|
|
|
if (string.IsNullOrEmpty(paymentEntity.CryptoPaymentDataType))
|
|
|
|
|
{
|
|
|
|
|
// For invoices created when CryptoPaymentDataType was not existing, we just consider that it is a RBFed payment for safety
|
|
|
|
|
paymentData = new BitcoinLikePaymentData();
|
|
|
|
|
paymentData.Outpoint = paymentEntity.Outpoint;
|
|
|
|
|
paymentData.Output = paymentEntity.Output;
|
|
|
|
|
paymentData.RBF = true;
|
|
|
|
|
paymentData.ConfirmationCount = 0;
|
|
|
|
|
paymentData.Legacy = true;
|
|
|
|
|
return paymentData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
paymentData =
|
|
|
|
|
JsonConvert.DeserializeObject<BitcoinLikePaymentData>(paymentEntity.CryptoPaymentData);
|
|
|
|
|
// legacy
|
|
|
|
|
paymentData.Output = paymentEntity.Output;
|
|
|
|
|
paymentData.Outpoint = paymentEntity.Outpoint;
|
|
|
|
|
#pragma warning restore CS0618
|
|
|
|
|
return paymentData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string GetPaymentMethodName(BTCPayNetworkBase network)
|
|
|
|
|
{
|
|
|
|
|
return network.DisplayName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override object PreparePayment(DerivationSchemeSettings supportedPaymentMethod, StoreData store,
|
|
|
|
|
BTCPayNetworkBase network)
|
2018-08-21 13:54:52 +09:00
|
|
|
|
{
|
|
|
|
|
return new Prepare()
|
|
|
|
|
{
|
|
|
|
|
GetFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(),
|
2019-05-29 14:33:31 +00:00
|
|
|
|
ReserveAddress = _WalletProvider.GetWallet(network)
|
|
|
|
|
.ReserveAddressAsync(supportedPaymentMethod.AccountDerivation)
|
2018-08-21 13:54:52 +09:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-24 06:38:47 +00:00
|
|
|
|
public override string PrettyDescription => "On-Chain";
|
|
|
|
|
public override PaymentTypes PaymentType => PaymentTypes.BTCLike;
|
|
|
|
|
|
2019-05-29 09:43:50 +00:00
|
|
|
|
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
|
|
|
|
|
DerivationSchemeSettings supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store,
|
2019-05-29 14:33:31 +00:00
|
|
|
|
BTCPayNetwork network, object preparePaymentObject)
|
2018-02-20 12:45:04 +09:00
|
|
|
|
{
|
2018-03-28 22:37:01 +09:00
|
|
|
|
if (!_ExplorerProvider.IsAvailable(network))
|
|
|
|
|
throw new PaymentMethodUnavailableException($"Full node not available");
|
2018-08-21 13:54:52 +09:00
|
|
|
|
var prepare = (Prepare)preparePaymentObject;
|
2019-05-29 14:33:31 +00:00
|
|
|
|
Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod =
|
|
|
|
|
new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod();
|
2019-01-05 00:37:09 +09:00
|
|
|
|
onchainMethod.NetworkFeeMode = store.GetStoreBlob().NetworkFeeMode;
|
2018-08-21 13:54:52 +09:00
|
|
|
|
onchainMethod.FeeRate = await prepare.GetFeeRate;
|
2019-01-05 00:37:09 +09:00
|
|
|
|
switch (onchainMethod.NetworkFeeMode)
|
|
|
|
|
{
|
|
|
|
|
case NetworkFeeMode.Always:
|
2019-01-07 15:35:18 +09:00
|
|
|
|
onchainMethod.NextNetworkFee = onchainMethod.FeeRate.GetFee(100); // assume price for 100 bytes
|
2019-01-05 00:37:09 +09:00
|
|
|
|
break;
|
|
|
|
|
case NetworkFeeMode.Never:
|
|
|
|
|
case NetworkFeeMode.MultiplePaymentsOnly:
|
2019-01-07 15:35:18 +09:00
|
|
|
|
onchainMethod.NextNetworkFee = Money.Zero;
|
2019-01-05 00:37:09 +09:00
|
|
|
|
break;
|
|
|
|
|
}
|
2018-08-21 13:54:52 +09:00
|
|
|
|
onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString();
|
2018-02-20 12:45:04 +09:00
|
|
|
|
return onchainMethod;
|
|
|
|
|
}
|
2019-05-29 14:33:31 +00:00
|
|
|
|
|
|
|
|
|
public override void PrepareInvoiceDto(InvoiceResponse invoiceResponse, InvoiceEntity invoiceEntity,
|
|
|
|
|
InvoiceCryptoInfo invoiceCryptoInfo,
|
|
|
|
|
PaymentMethodAccounting accounting, PaymentMethod info)
|
|
|
|
|
{
|
|
|
|
|
var scheme = info.Network.UriScheme;
|
|
|
|
|
|
|
|
|
|
var minerInfo = new MinerFeeInfo();
|
|
|
|
|
minerInfo.TotalFee = accounting.NetworkFee.Satoshi;
|
|
|
|
|
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)info.GetPaymentMethodDetails()).FeeRate
|
|
|
|
|
.GetFee(1).Satoshi;
|
|
|
|
|
invoiceResponse.MinerFees.TryAdd(invoiceCryptoInfo.CryptoCode, minerInfo);
|
|
|
|
|
invoiceCryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
|
|
|
|
|
{
|
|
|
|
|
BIP21 = $"{scheme}:{invoiceCryptoInfo.Address}?amount={invoiceCryptoInfo.Due}",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#pragma warning disable 618
|
|
|
|
|
if (info.CryptoCode == "BTC")
|
|
|
|
|
{
|
|
|
|
|
invoiceResponse.BTCPrice = invoiceCryptoInfo.Price;
|
|
|
|
|
invoiceResponse.Rate = invoiceCryptoInfo.Rate;
|
|
|
|
|
invoiceResponse.ExRates = invoiceCryptoInfo.ExRates;
|
|
|
|
|
invoiceResponse.BitcoinAddress = invoiceCryptoInfo.Address;
|
|
|
|
|
invoiceResponse.BTCPaid = invoiceCryptoInfo.Paid;
|
|
|
|
|
invoiceResponse.BTCDue = invoiceCryptoInfo.Due;
|
|
|
|
|
invoiceResponse.PaymentUrls = invoiceCryptoInfo.PaymentUrls;
|
|
|
|
|
}
|
|
|
|
|
#pragma warning restore 618
|
|
|
|
|
}
|
2018-02-20 12:45:04 +09:00
|
|
|
|
}
|
|
|
|
|
}
|