2017-09-13 08:47:34 +02:00
|
|
|
|
using BTCPayServer.Authentication;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using BTCPayServer.Logging;
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using NBitpayClient;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using BTCPayServer.Models;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using NBitcoin;
|
|
|
|
|
using NBitcoin.DataEncoders;
|
|
|
|
|
using BTCPayServer.Filters;
|
|
|
|
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using Microsoft.AspNetCore.Identity;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
|
|
|
using NBitcoin.Payment;
|
|
|
|
|
using BTCPayServer.Data;
|
|
|
|
|
using BTCPayServer.Models.InvoicingModels;
|
|
|
|
|
using System.Security.Claims;
|
|
|
|
|
using BTCPayServer.Services;
|
|
|
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
|
|
using System.Text.RegularExpressions;
|
2017-09-15 09:06:57 +02:00
|
|
|
|
using BTCPayServer.Services.Stores;
|
2017-10-20 21:06:37 +02:00
|
|
|
|
using BTCPayServer.Services.Invoices;
|
2017-09-15 09:06:57 +02:00
|
|
|
|
using BTCPayServer.Services.Rates;
|
|
|
|
|
using BTCPayServer.Services.Wallets;
|
2017-09-27 07:18:09 +02:00
|
|
|
|
using BTCPayServer.Validations;
|
2017-10-11 05:20:44 +02:00
|
|
|
|
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2017-09-27 08:16:30 +02:00
|
|
|
|
using Microsoft.AspNetCore.Mvc.Routing;
|
2017-10-12 09:33:53 +02:00
|
|
|
|
using NBXplorer.DerivationStrategy;
|
2017-10-12 17:25:45 +02:00
|
|
|
|
using NBXplorer;
|
2018-01-07 18:36:41 +01:00
|
|
|
|
using BTCPayServer.HostedServices;
|
2017-09-13 08:47:34 +02:00
|
|
|
|
|
|
|
|
|
namespace BTCPayServer.Controllers
|
|
|
|
|
{
|
2017-10-27 10:53:04 +02:00
|
|
|
|
public partial class InvoiceController : Controller
|
|
|
|
|
{
|
|
|
|
|
InvoiceRepository _InvoiceRepository;
|
2018-01-11 06:36:12 +01:00
|
|
|
|
BTCPayWalletProvider _WalletProvider;
|
2018-01-08 18:57:06 +01:00
|
|
|
|
IRateProviderFactory _RateProviders;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
StoreRepository _StoreRepository;
|
|
|
|
|
UserManager<ApplicationUser> _UserManager;
|
2017-12-21 07:52:04 +01:00
|
|
|
|
IFeeProviderFactory _FeeProviderFactory;
|
2017-10-27 11:58:43 +02:00
|
|
|
|
private CurrencyNameTable _CurrencyNameTable;
|
2017-12-17 11:58:55 +01:00
|
|
|
|
EventAggregator _EventAggregator;
|
2017-12-21 07:52:04 +01:00
|
|
|
|
BTCPayNetworkProvider _NetworkProvider;
|
2018-01-07 18:36:41 +01:00
|
|
|
|
ExplorerClientProvider _ExplorerClients;
|
2017-12-21 07:52:04 +01:00
|
|
|
|
public InvoiceController(InvoiceRepository invoiceRepository,
|
2017-10-27 11:58:43 +02:00
|
|
|
|
CurrencyNameTable currencyNameTable,
|
2017-10-27 10:53:04 +02:00
|
|
|
|
UserManager<ApplicationUser> userManager,
|
2018-01-11 06:36:12 +01:00
|
|
|
|
BTCPayWalletProvider walletProvider,
|
2018-01-08 18:57:06 +01:00
|
|
|
|
IRateProviderFactory rateProviders,
|
2017-10-27 10:53:04 +02:00
|
|
|
|
StoreRepository storeRepository,
|
2017-12-17 11:58:55 +01:00
|
|
|
|
EventAggregator eventAggregator,
|
2017-12-21 07:52:04 +01:00
|
|
|
|
BTCPayNetworkProvider networkProvider,
|
2018-01-07 18:36:41 +01:00
|
|
|
|
ExplorerClientProvider explorerClientProviders,
|
2017-12-21 07:52:04 +01:00
|
|
|
|
IFeeProviderFactory feeProviderFactory)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2018-01-07 18:36:41 +01:00
|
|
|
|
_ExplorerClients = explorerClientProviders;
|
2017-10-27 11:58:43 +02:00
|
|
|
|
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
2017-10-27 10:53:04 +02:00
|
|
|
|
_StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository));
|
|
|
|
|
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
2018-01-11 06:36:12 +01:00
|
|
|
|
_WalletProvider = walletProvider ?? throw new ArgumentNullException(nameof(walletProvider));
|
2018-01-08 18:57:06 +01:00
|
|
|
|
_RateProviders = rateProviders ?? throw new ArgumentNullException(nameof(rateProviders));
|
2017-10-27 10:53:04 +02:00
|
|
|
|
_UserManager = userManager;
|
2017-12-21 07:52:04 +01:00
|
|
|
|
_FeeProviderFactory = feeProviderFactory ?? throw new ArgumentNullException(nameof(feeProviderFactory));
|
2017-12-17 11:58:55 +01:00
|
|
|
|
_EventAggregator = eventAggregator;
|
2017-12-21 07:52:04 +01:00
|
|
|
|
_NetworkProvider = networkProvider;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
}
|
2017-09-13 08:47:34 +02:00
|
|
|
|
|
2017-12-21 07:52:04 +01:00
|
|
|
|
|
2017-12-03 06:43:52 +01:00
|
|
|
|
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2018-01-06 10:57:56 +01:00
|
|
|
|
var derivationStrategies = store.GetDerivationStrategies(_NetworkProvider).ToList();
|
|
|
|
|
if (derivationStrategies.Count == 0)
|
|
|
|
|
throw new BitpayHttpException(400, "This store has not configured the derivation strategy");
|
2017-10-27 10:53:04 +02:00
|
|
|
|
var entity = new InvoiceEntity
|
|
|
|
|
{
|
2018-01-06 10:57:56 +01:00
|
|
|
|
InvoiceTime = DateTimeOffset.UtcNow
|
2017-10-27 10:53:04 +02:00
|
|
|
|
};
|
2018-01-06 10:57:56 +01:00
|
|
|
|
entity.SetDerivationStrategies(derivationStrategies);
|
|
|
|
|
|
2017-12-21 07:52:04 +01:00
|
|
|
|
var storeBlob = store.GetStoreBlob();
|
2017-10-27 10:53:04 +02:00
|
|
|
|
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(expiryMinutes);
|
2017-12-03 06:43:52 +01:00
|
|
|
|
entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration);
|
2017-10-27 10:53:04 +02:00
|
|
|
|
entity.OrderId = invoice.OrderId;
|
|
|
|
|
entity.ServerUrl = serverUrl;
|
2018-01-07 20:14:35 +01:00
|
|
|
|
entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications;
|
|
|
|
|
entity.ExtendedNotifications = invoice.ExtendedNotifications;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
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);
|
2017-11-12 15:23:21 +01:00
|
|
|
|
|
2018-01-06 10:57:56 +01:00
|
|
|
|
var queries = derivationStrategies
|
2018-01-12 08:30:34 +01:00
|
|
|
|
.Select(derivationStrategy => (Wallet: _WalletProvider.GetWallet(derivationStrategy.Network),
|
2018-01-11 06:36:12 +01:00
|
|
|
|
DerivationStrategy: derivationStrategy.DerivationStrategyBase,
|
|
|
|
|
Network: derivationStrategy.Network,
|
|
|
|
|
RateProvider: _RateProviders.GetRateProvider(derivationStrategy.Network),
|
|
|
|
|
FeeRateProvider: _FeeProviderFactory.CreateFeeProvider(derivationStrategy.Network)))
|
2018-01-12 08:30:34 +01:00
|
|
|
|
.Where(_ => _.Wallet != null &&
|
|
|
|
|
_.FeeRateProvider != null &&
|
2018-01-11 09:29:48 +01:00
|
|
|
|
_.RateProvider != null)
|
2018-01-11 06:36:12 +01:00
|
|
|
|
.Select(_ =>
|
2017-12-21 07:52:04 +01:00
|
|
|
|
{
|
|
|
|
|
return new
|
|
|
|
|
{
|
2018-01-11 06:36:12 +01:00
|
|
|
|
network = _.Network,
|
|
|
|
|
getFeeRate = _.FeeRateProvider.GetFeeRateAsync(),
|
|
|
|
|
getRate = _.RateProvider.GetRateAsync(invoice.Currency),
|
|
|
|
|
getAddress = _.Wallet.ReserveAddressAsync(_.DerivationStrategy)
|
2017-12-21 07:52:04 +01:00
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2018-01-12 08:30:34 +01:00
|
|
|
|
bool legacyBTCisSet = false;
|
2017-12-21 07:52:04 +01:00
|
|
|
|
var cryptoDatas = new Dictionary<string, CryptoData>();
|
|
|
|
|
foreach (var q in queries)
|
|
|
|
|
{
|
|
|
|
|
CryptoData cryptoData = new CryptoData();
|
|
|
|
|
cryptoData.CryptoCode = q.network.CryptoCode;
|
|
|
|
|
cryptoData.FeeRate = (await q.getFeeRate);
|
2018-01-12 08:30:34 +01:00
|
|
|
|
cryptoData.TxFee = GetTxFee(storeBlob, cryptoData.FeeRate); // assume price for 100 bytes
|
2017-12-21 07:52:04 +01:00
|
|
|
|
cryptoData.Rate = await q.getRate;
|
|
|
|
|
cryptoData.DepositAddress = (await q.getAddress).ToString();
|
|
|
|
|
|
|
|
|
|
#pragma warning disable CS0618
|
2018-01-06 10:57:56 +01:00
|
|
|
|
if (q.network.IsBTC)
|
2017-12-21 07:52:04 +01:00
|
|
|
|
{
|
2018-01-12 08:30:34 +01:00
|
|
|
|
legacyBTCisSet = true;
|
2017-12-21 07:52:04 +01:00
|
|
|
|
entity.TxFee = cryptoData.TxFee;
|
|
|
|
|
entity.Rate = cryptoData.Rate;
|
|
|
|
|
entity.DepositAddress = cryptoData.DepositAddress;
|
|
|
|
|
}
|
|
|
|
|
#pragma warning restore CS0618
|
|
|
|
|
cryptoDatas.Add(cryptoData.CryptoCode, cryptoData);
|
|
|
|
|
}
|
2018-01-12 08:30:34 +01:00
|
|
|
|
|
|
|
|
|
if (!legacyBTCisSet)
|
|
|
|
|
{
|
|
|
|
|
// Legacy Bitpay clients expect information for BTC information, even if the store do not support it
|
|
|
|
|
#pragma warning disable CS0618
|
|
|
|
|
var btc = _NetworkProvider.BTC;
|
|
|
|
|
var feeProvider = _FeeProviderFactory.CreateFeeProvider(btc);
|
|
|
|
|
var rateProvider = _RateProviders.GetRateProvider(btc);
|
|
|
|
|
if (feeProvider != null && rateProvider != null)
|
|
|
|
|
{
|
|
|
|
|
var gettingFee = feeProvider.GetFeeRateAsync();
|
2018-01-14 07:26:14 +01:00
|
|
|
|
var gettingRate = rateProvider.GetRateAsync(invoice.Currency);
|
2018-01-12 08:30:34 +01:00
|
|
|
|
entity.TxFee = GetTxFee(storeBlob, await gettingFee);
|
|
|
|
|
entity.Rate = await gettingRate;
|
|
|
|
|
}
|
|
|
|
|
#pragma warning restore CS0618
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-21 07:52:04 +01:00
|
|
|
|
entity.SetCryptoData(cryptoDatas);
|
2017-10-27 10:53:04 +02:00
|
|
|
|
entity.PosData = invoice.PosData;
|
2017-12-21 07:52:04 +01:00
|
|
|
|
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
|
2018-01-07 18:36:41 +01:00
|
|
|
|
_EventAggregator.Publish(new Events.InvoiceCreatedEvent(entity.Id));
|
2017-12-21 07:52:04 +01:00
|
|
|
|
var resp = entity.EntityToDTO(_NetworkProvider);
|
2017-10-27 10:53:04 +02:00
|
|
|
|
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
|
|
|
|
}
|
2017-09-13 08:47:34 +02:00
|
|
|
|
|
2018-01-12 08:30:34 +01:00
|
|
|
|
private static Money GetTxFee(StoreBlob storeBlob, FeeRate feeRate)
|
|
|
|
|
{
|
|
|
|
|
return storeBlob.NetworkFeeDisabled ? Money.Zero : feeRate.GetFee(100);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
private SpeedPolicy ParseSpeedPolicy(string transactionSpeed, SpeedPolicy defaultPolicy)
|
|
|
|
|
{
|
|
|
|
|
if (transactionSpeed == null)
|
|
|
|
|
return defaultPolicy;
|
|
|
|
|
var mappings = new Dictionary<string, SpeedPolicy>();
|
|
|
|
|
mappings.Add("low", SpeedPolicy.LowSpeed);
|
|
|
|
|
mappings.Add("medium", SpeedPolicy.MediumSpeed);
|
|
|
|
|
mappings.Add("high", SpeedPolicy.HighSpeed);
|
|
|
|
|
if (!mappings.TryGetValue(transactionSpeed, out SpeedPolicy policy))
|
|
|
|
|
policy = defaultPolicy;
|
|
|
|
|
return policy;
|
|
|
|
|
}
|
2017-10-23 07:51:21 +02:00
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
private void FillBuyerInfo(Buyer buyer, BuyerInformation buyerInformation)
|
|
|
|
|
{
|
|
|
|
|
if (buyer == null)
|
|
|
|
|
return;
|
|
|
|
|
buyerInformation.BuyerAddress1 = buyerInformation.BuyerAddress1 ?? buyer.Address1;
|
|
|
|
|
buyerInformation.BuyerAddress2 = buyerInformation.BuyerAddress2 ?? buyer.Address2;
|
|
|
|
|
buyerInformation.BuyerCity = buyerInformation.BuyerCity ?? buyer.City;
|
|
|
|
|
buyerInformation.BuyerCountry = buyerInformation.BuyerCountry ?? buyer.country;
|
|
|
|
|
buyerInformation.BuyerEmail = buyerInformation.BuyerEmail ?? buyer.email;
|
|
|
|
|
buyerInformation.BuyerName = buyerInformation.BuyerName ?? buyer.Name;
|
|
|
|
|
buyerInformation.BuyerPhone = buyerInformation.BuyerPhone ?? buyer.phone;
|
|
|
|
|
buyerInformation.BuyerState = buyerInformation.BuyerState ?? buyer.State;
|
|
|
|
|
buyerInformation.BuyerZip = buyerInformation.BuyerZip ?? buyer.zip;
|
|
|
|
|
}
|
2017-10-13 09:44:55 +02:00
|
|
|
|
|
2017-12-21 07:52:04 +01:00
|
|
|
|
private DerivationStrategyBase ParseDerivationStrategy(string derivationStrategy, BTCPayNetwork network)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2017-12-21 07:52:04 +01:00
|
|
|
|
return new DerivationStrategyFactory(network.NBitcoinNetwork).Parse(derivationStrategy);
|
2017-10-27 10:53:04 +02:00
|
|
|
|
}
|
2017-10-12 09:33:53 +02:00
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
private TDest Map<TFrom, TDest>(TFrom data)
|
|
|
|
|
{
|
|
|
|
|
return JsonConvert.DeserializeObject<TDest>(JsonConvert.SerializeObject(data));
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-13 08:47:34 +02:00
|
|
|
|
}
|