mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 14:22:40 +01:00
Can tweak the rate at store level
This commit is contained in:
parent
2b31af80cb
commit
5f6913b3a2
8 changed files with 163 additions and 8 deletions
|
@ -61,10 +61,9 @@ namespace BTCPayServer.Tests
|
||||||
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
|
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
|
||||||
StoreId = store.CreatedStoreId;
|
StoreId = store.CreatedStoreId;
|
||||||
DerivationScheme = new DerivationStrategyFactory(SupportedNetwork.NBitcoinNetwork).Parse(ExtKey.Neuter().ToString() + "-[legacy]");
|
DerivationScheme = new DerivationStrategyFactory(SupportedNetwork.NBitcoinNetwork).Parse(ExtKey.Neuter().ToString() + "-[legacy]");
|
||||||
await store.UpdateStore(StoreId, new StoreViewModel()
|
var vm = (StoreViewModel)((ViewResult)await store.UpdateStore(StoreId)).Model;
|
||||||
{
|
vm.SpeedPolicy = SpeedPolicy.MediumSpeed;
|
||||||
SpeedPolicy = SpeedPolicy.MediumSpeed
|
await store.UpdateStore(StoreId, vm);
|
||||||
});
|
|
||||||
|
|
||||||
await store.AddDerivationScheme(StoreId, new DerivationSchemeViewModel()
|
await store.AddDerivationScheme(StoreId, new DerivationSchemeViewModel()
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,6 +24,7 @@ using BTCPayServer.Services.Rates;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using BTCPayServer.Eclair;
|
using BTCPayServer.Eclair;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
|
@ -392,6 +393,50 @@ namespace BTCPayServer.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanTweakRate()
|
||||||
|
{
|
||||||
|
using (var tester = ServerTester.Create())
|
||||||
|
{
|
||||||
|
tester.Start();
|
||||||
|
var user = tester.NewAccount();
|
||||||
|
user.GrantAccess();
|
||||||
|
|
||||||
|
|
||||||
|
// First we try payment with a merchant having only BTC
|
||||||
|
var invoice1 = user.BitPay.CreateInvoice(new Invoice()
|
||||||
|
{
|
||||||
|
Price = 5000.0,
|
||||||
|
Currency = "USD",
|
||||||
|
PosData = "posData",
|
||||||
|
OrderId = "orderId",
|
||||||
|
ItemDesc = "Some description",
|
||||||
|
FullNotifications = true
|
||||||
|
}, Facade.Merchant);
|
||||||
|
|
||||||
|
|
||||||
|
var storeController = tester.PayTester.GetController<StoresController>(user.UserId);
|
||||||
|
var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId).Result).Model;
|
||||||
|
Assert.Equal(1.0, vm.RateMultiplier);
|
||||||
|
vm.RateMultiplier = 0.5;
|
||||||
|
storeController.UpdateStore(user.StoreId, vm).Wait();
|
||||||
|
|
||||||
|
|
||||||
|
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
|
||||||
|
{
|
||||||
|
Price = 5000.0,
|
||||||
|
Currency = "USD",
|
||||||
|
PosData = "posData",
|
||||||
|
OrderId = "orderId",
|
||||||
|
ItemDesc = "Some description",
|
||||||
|
FullNotifications = true
|
||||||
|
}, Facade.Merchant);
|
||||||
|
|
||||||
|
Assert.True(invoice2.BtcPrice.Almost(invoice1.BtcPrice * 2, 0.00001m));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanHaveLTCOnlyStore()
|
public void CanHaveLTCOnlyStore()
|
||||||
{
|
{
|
||||||
|
|
|
@ -130,7 +130,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
network = _.Network,
|
network = _.Network,
|
||||||
getFeeRate = _.FeeRateProvider.GetFeeRateAsync(),
|
getFeeRate = _.FeeRateProvider.GetFeeRateAsync(),
|
||||||
getRate = _.RateProvider.GetRateAsync(invoice.Currency),
|
getRate = storeBlob.ApplyRateRules(_.Network, _.RateProvider).GetRateAsync(invoice.Currency),
|
||||||
getAddress = _.Wallet.ReserveAddressAsync(_.DerivationStrategy)
|
getAddress = _.Wallet.ReserveAddressAsync(_.DerivationStrategy)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -164,7 +164,7 @@ namespace BTCPayServer.Controllers
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
var btc = _NetworkProvider.BTC;
|
var btc = _NetworkProvider.BTC;
|
||||||
var feeProvider = _FeeProviderFactory.CreateFeeProvider(btc);
|
var feeProvider = _FeeProviderFactory.CreateFeeProvider(btc);
|
||||||
var rateProvider = _RateProviders.GetRateProvider(btc);
|
var rateProvider = storeBlob.ApplyRateRules(btc, _RateProviders.GetRateProvider(btc));
|
||||||
if (feeProvider != null && rateProvider != null)
|
if (feeProvider != null && rateProvider != null)
|
||||||
{
|
{
|
||||||
var gettingFee = feeProvider.GetFeeRateAsync();
|
var gettingFee = feeProvider.GetFeeRateAsync();
|
||||||
|
|
|
@ -164,6 +164,7 @@ namespace BTCPayServer.Controllers
|
||||||
vm.StatusMessage = StatusMessage;
|
vm.StatusMessage = StatusMessage;
|
||||||
vm.MonitoringExpiration = storeBlob.MonitoringExpiration;
|
vm.MonitoringExpiration = storeBlob.MonitoringExpiration;
|
||||||
vm.InvoiceExpiration = storeBlob.InvoiceExpiration;
|
vm.InvoiceExpiration = storeBlob.InvoiceExpiration;
|
||||||
|
vm.RateMultiplier = (double)storeBlob.GetRateMultiplier();
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,6 +308,8 @@ namespace BTCPayServer.Controllers
|
||||||
var blob = store.GetStoreBlob();
|
var blob = store.GetStoreBlob();
|
||||||
blob.NetworkFeeDisabled = !model.NetworkFee;
|
blob.NetworkFeeDisabled = !model.NetworkFee;
|
||||||
blob.MonitoringExpiration = model.MonitoringExpiration;
|
blob.MonitoringExpiration = model.MonitoringExpiration;
|
||||||
|
blob.InvoiceExpiration = model.InvoiceExpiration;
|
||||||
|
blob.SetRateMultiplier(model.RateMultiplier);
|
||||||
|
|
||||||
if (store.SetStoreBlob(blob))
|
if (store.SetStoreBlob(blob))
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,6 +11,7 @@ using System.Threading.Tasks;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using BTCPayServer.Services.Rates;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
|
@ -99,10 +100,10 @@ namespace BTCPayServer.Data
|
||||||
|
|
||||||
if (!existing && string.IsNullOrEmpty(derivationScheme))
|
if (!existing && string.IsNullOrEmpty(derivationScheme))
|
||||||
{
|
{
|
||||||
if(network.IsBTC)
|
if (network.IsBTC)
|
||||||
DerivationStrategy = null;
|
DerivationStrategy = null;
|
||||||
}
|
}
|
||||||
else if(!existing)
|
else if (!existing)
|
||||||
strategies.Add(new JProperty(network.CryptoCode, new JValue(derivationScheme)));
|
strategies.Add(new JProperty(network.CryptoCode, new JValue(derivationScheme)));
|
||||||
// This is deprecated so we don't have to set anymore
|
// This is deprecated so we don't have to set anymore
|
||||||
//if (network.IsBTC)
|
//if (network.IsBTC)
|
||||||
|
@ -173,6 +174,22 @@ namespace BTCPayServer.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class RateRule
|
||||||
|
{
|
||||||
|
public RateRule()
|
||||||
|
{
|
||||||
|
RuleName = "Multiplier";
|
||||||
|
}
|
||||||
|
public string RuleName { get; set; }
|
||||||
|
|
||||||
|
public double Multiplier { get; set; }
|
||||||
|
|
||||||
|
public decimal Apply(BTCPayNetwork network, decimal rate)
|
||||||
|
{
|
||||||
|
return rate * (decimal)Multiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class StoreBlob
|
public class StoreBlob
|
||||||
{
|
{
|
||||||
public StoreBlob()
|
public StoreBlob()
|
||||||
|
@ -200,5 +217,30 @@ namespace BTCPayServer.Data
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetRateMultiplier(double rate)
|
||||||
|
{
|
||||||
|
RateRules = new List<RateRule>();
|
||||||
|
RateRules.Add(new RateRule() { Multiplier = rate });
|
||||||
|
}
|
||||||
|
public decimal GetRateMultiplier()
|
||||||
|
{
|
||||||
|
decimal rate = 1.0m;
|
||||||
|
if (RateRules == null || RateRules.Count == 0)
|
||||||
|
return rate;
|
||||||
|
foreach (var rule in RateRules)
|
||||||
|
{
|
||||||
|
rate = rule.Apply(null, rate);
|
||||||
|
}
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RateRule> RateRules { get; set; } = new List<RateRule>();
|
||||||
|
|
||||||
|
public IRateProvider ApplyRateRules(BTCPayNetwork network, IRateProvider rateProvider)
|
||||||
|
{
|
||||||
|
if (RateRules == null || RateRules.Count == 0)
|
||||||
|
return rateProvider;
|
||||||
|
return new TweakRateProvider(network, rateProvider, RateRules.ToList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,14 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||||
|
|
||||||
public List<StoreViewModel.DerivationScheme> DerivationSchemes { get; set; } = new List<StoreViewModel.DerivationScheme>();
|
public List<StoreViewModel.DerivationScheme> DerivationSchemes { get; set; } = new List<StoreViewModel.DerivationScheme>();
|
||||||
|
|
||||||
|
[Display(Name = "Multiply the original rate by ...")]
|
||||||
|
[Range(0.01, 10.0)]
|
||||||
|
public double RateMultiplier
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
[Display(Name = "Invoice expires if the full amount has not been paid after ... minutes")]
|
[Display(Name = "Invoice expires if the full amount has not been paid after ... minutes")]
|
||||||
[Range(1, 60 * 24 * 31)]
|
[Range(1, 60 * 24 * 31)]
|
||||||
public int InvoiceExpiration
|
public int InvoiceExpiration
|
||||||
|
|
53
BTCPayServer/Services/Rates/TweakRateProvider.cs
Normal file
53
BTCPayServer/Services/Rates/TweakRateProvider.cs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Services.Rates
|
||||||
|
{
|
||||||
|
public class TweakRateProvider : IRateProvider
|
||||||
|
{
|
||||||
|
private BTCPayNetwork network;
|
||||||
|
private IRateProvider rateProvider;
|
||||||
|
private List<RateRule> rateRules;
|
||||||
|
|
||||||
|
public TweakRateProvider(BTCPayNetwork network, IRateProvider rateProvider, List<RateRule> rateRules)
|
||||||
|
{
|
||||||
|
if (network == null)
|
||||||
|
throw new ArgumentNullException(nameof(network));
|
||||||
|
if (rateProvider == null)
|
||||||
|
throw new ArgumentNullException(nameof(rateProvider));
|
||||||
|
if (rateRules == null)
|
||||||
|
throw new ArgumentNullException(nameof(rateRules));
|
||||||
|
this.network = network;
|
||||||
|
this.rateProvider = rateProvider;
|
||||||
|
this.rateRules = rateRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<decimal> GetRateAsync(string currency)
|
||||||
|
{
|
||||||
|
var rate = await rateProvider.GetRateAsync(currency);
|
||||||
|
foreach(var rule in rateRules)
|
||||||
|
{
|
||||||
|
rate = rule.Apply(network, rate);
|
||||||
|
}
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ICollection<Rate>> GetRatesAsync()
|
||||||
|
{
|
||||||
|
List<Rate> rates = new List<Rate>();
|
||||||
|
foreach (var rate in await rateProvider.GetRatesAsync())
|
||||||
|
{
|
||||||
|
var localRate = rate.Value;
|
||||||
|
foreach (var rule in rateRules)
|
||||||
|
{
|
||||||
|
localRate = rule.Apply(network, localRate);
|
||||||
|
}
|
||||||
|
rates.Add(new Rate(rate.Currency, localRate));
|
||||||
|
}
|
||||||
|
return rates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,11 @@
|
||||||
<label asp-for="NetworkFee"></label>
|
<label asp-for="NetworkFee"></label>
|
||||||
<input asp-for="NetworkFee" type="checkbox" class="form-check" />
|
<input asp-for="NetworkFee" type="checkbox" class="form-check" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="RateMultiplier"></label>
|
||||||
|
<input asp-for="RateMultiplier" class="form-control" />
|
||||||
|
<span asp-validation-for="RateMultiplier" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="InvoiceExpiration"></label>
|
<label asp-for="InvoiceExpiration"></label>
|
||||||
<input asp-for="InvoiceExpiration" class="form-control" />
|
<input asp-for="InvoiceExpiration" class="form-control" />
|
||||||
|
|
Loading…
Add table
Reference in a new issue