Remove Changelly

This commit is contained in:
Kukks 2020-09-08 15:39:59 +02:00
parent 7ca0a8c56c
commit 50a9772388
22 changed files with 6 additions and 1054 deletions

View file

@ -1,242 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments.Changelly;
using BTCPayServer.Payments.Changelly.Models;
using BTCPayServer.Services.Rates;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Xunit;
using Xunit.Abstractions;
namespace BTCPayServer.Tests
{
public class ChangellyTests
{
public const int TestTimeout = 60_000;
public ChangellyTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CanSetChangellyPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.Null(storeBlob.ChangellySettings);
var updateModel = new UpdateChangellySettingsViewModel()
{
ApiSecret = "secret",
ApiKey = "key",
ApiUrl = "http://gozo.com",
ChangellyMerchantId = "aaa",
};
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.NotNull(storeBlob.ChangellySettings);
Assert.NotNull(storeBlob.ChangellySettings);
Assert.IsType<ChangellySettings>(storeBlob.ChangellySettings);
Assert.Equal(storeBlob.ChangellySettings.ApiKey, updateModel.ApiKey);
Assert.Equal(storeBlob.ChangellySettings.ApiSecret,
updateModel.ApiSecret);
Assert.Equal(storeBlob.ChangellySettings.ApiUrl, updateModel.ApiUrl);
Assert.Equal(storeBlob.ChangellySettings.ChangellyMerchantId,
updateModel.ChangellyMerchantId);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanToggleChangellyPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var updateModel = new UpdateChangellySettingsViewModel()
{
ApiSecret = "secret",
ApiKey = "key",
ApiUrl = "http://gozo.com",
ChangellyMerchantId = "aaa",
Enabled = true
};
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
Assert.True(store.GetStoreBlob().ChangellySettings.Enabled);
updateModel.Enabled = false;
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
Assert.False(store.GetStoreBlob().ChangellySettings.Enabled);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CannotUseChangellyApiWithoutChangellyPaymentMethodSet()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var changellyController =
tester.PayTester.GetController<ChangellyController>(user.UserId, user.StoreId);
changellyController.IsTest = true;
//test non existing payment method
Assert.IsType<BitpayErrorModel>(Assert
.IsType<BadRequestObjectResult>(await changellyController.GetCurrencyList(user.StoreId))
.Value);
var updateModel = CreateDefaultChangellyParams(false);
var storesController = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
//set payment method but disabled
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await storesController.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
Assert.IsType<BitpayErrorModel>(Assert
.IsType<BadRequestObjectResult>(await changellyController.GetCurrencyList(user.StoreId))
.Value);
updateModel.Enabled = true;
//test with enabled method
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await storesController.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
Assert.IsNotType<BitpayErrorModel>(Assert
.IsType<OkObjectResult>(await changellyController.GetCurrencyList(user.StoreId))
.Value);
}
}
UpdateChangellySettingsViewModel CreateDefaultChangellyParams(bool enabled)
{
return new UpdateChangellySettingsViewModel()
{
ApiKey = "6ed02cdf1b614d89a8c0ceb170eebb61",
ApiSecret = "8fbd66a2af5fd15a6b5f8ed0159c5842e32a18538521ffa145bd6c9e124d3483",
ChangellyMerchantId = "804298eb5753",
Enabled = enabled
};
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CanGetCurrencyListFromChangelly()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
//save changelly settings
var updateModel = CreateDefaultChangellyParams(true);
var storesController = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
//confirm saved
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await storesController.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
var factory = UnitTest1.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
var httpClientFactory = TestUtils.CreateHttpFactory();
var changellyController = new ChangellyController(
new ChangellyClientProvider(tester.PayTester.StoreRepository, httpClientFactory),
tester.NetworkProvider, fetcher);
changellyController.IsTest = true;
var result = Assert
.IsType<OkObjectResult>(await changellyController.GetCurrencyList(user.StoreId))
.Value as IEnumerable<CurrencyFull>;
Assert.True(result.Any());
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CanCalculateToAmountForChangelly()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var updateModel = CreateDefaultChangellyParams(true);
var storesController = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await storesController.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
var factory = UnitTest1.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
var httpClientFactory = TestUtils.CreateHttpFactory();
var changellyController = new ChangellyController(
new ChangellyClientProvider(tester.PayTester.StoreRepository, httpClientFactory),
tester.NetworkProvider, fetcher);
changellyController.IsTest = true;
Assert.IsType<decimal>(Assert
.IsType<OkObjectResult>(await changellyController.CalculateAmount(user.StoreId, "ltc", "btc", 1.0m, default))
.Value);
}
}
[Fact]
[Trait("Fast", "Fast")]
public void CanComputeBaseAmount()
{
Assert.Equal(1, ChangellyCalculationHelper.ComputeBaseAmount(1, 1));
Assert.Equal(0.5m, ChangellyCalculationHelper.ComputeBaseAmount(1, 0.5m));
Assert.Equal(2, ChangellyCalculationHelper.ComputeBaseAmount(0.5m, 1));
Assert.Equal(4m, ChangellyCalculationHelper.ComputeBaseAmount(1, 4));
}
[Fact]
[Trait("Fast", "Fast")]
public void CanComputeCorrectAmount()
{
Assert.Equal(1, ChangellyCalculationHelper.ComputeCorrectAmount(0.5m, 1, 2));
Assert.Equal(0.25m, ChangellyCalculationHelper.ComputeCorrectAmount(0.5m, 1, 0.5m));
Assert.Equal(20, ChangellyCalculationHelper.ComputeCorrectAmount(10, 1, 2));
}
}
}

View file

@ -1,122 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Payments.Changelly;
using BTCPayServer.Rating;
using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
{
[Route("[controller]/{storeId}")]
public class ChangellyController : Controller
{
private readonly ChangellyClientProvider _changellyClientProvider;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly RateFetcher _RateProviderFactory;
public ChangellyController(ChangellyClientProvider changellyClientProvider,
BTCPayNetworkProvider btcPayNetworkProvider,
RateFetcher rateProviderFactory)
{
_RateProviderFactory = rateProviderFactory ?? throw new ArgumentNullException(nameof(rateProviderFactory));
_changellyClientProvider = changellyClientProvider;
_btcPayNetworkProvider = btcPayNetworkProvider;
}
[HttpGet]
[Route("currencies")]
public async Task<IActionResult> GetCurrencyList(string storeId)
{
try
{
var client = await TryGetChangellyClient(storeId);
return Ok(await client.GetCurrenciesFull());
}
catch (Exception e)
{
return BadRequest(new BitpayErrorModel()
{
Error = e.Message
});
}
}
[HttpGet]
[Route("calculate")]
public async Task<IActionResult> CalculateAmount(string storeId, string fromCurrency, string toCurrency,
decimal toCurrencyAmount, CancellationToken cancellationToken)
{
try
{
var client = await TryGetChangellyClient(storeId);
if (fromCurrency.Equals("usd", StringComparison.InvariantCultureIgnoreCase)
|| fromCurrency.Equals("eur", StringComparison.InvariantCultureIgnoreCase))
{
return await HandleCalculateFiatAmount(fromCurrency, toCurrency, toCurrencyAmount, cancellationToken);
}
var callCounter = 0;
var baseRate = await client.GetExchangeAmount(fromCurrency, toCurrency, 1);
var currentAmount = ChangellyCalculationHelper.ComputeBaseAmount(baseRate, toCurrencyAmount);
while (true)
{
if (callCounter > 10)
{
BadRequest();
}
var computedAmount = await client.GetExchangeAmount(fromCurrency, toCurrency, currentAmount);
callCounter++;
if (computedAmount < toCurrencyAmount)
{
currentAmount =
ChangellyCalculationHelper.ComputeCorrectAmount(currentAmount, computedAmount,
toCurrencyAmount);
}
else
{
return Ok(currentAmount);
}
}
}
catch (Exception e)
{
return BadRequest(new BitpayErrorModel()
{
Error = e.Message
});
}
}
private async Task<Changelly> TryGetChangellyClient(string storeId)
{
var store = IsTest ? null : HttpContext.GetStoreData();
storeId = storeId ?? store?.Id;
return await _changellyClientProvider.TryGetChangellyClient(storeId, store);
}
private async Task<IActionResult> HandleCalculateFiatAmount(string fromCurrency, string toCurrency,
decimal toCurrencyAmount, CancellationToken cancellationToken)
{
var store = HttpContext.GetStoreData();
var rules = store.GetStoreBlob().GetRateRules(_btcPayNetworkProvider);
var rate = await _RateProviderFactory.FetchRate(new CurrencyPair(toCurrency, fromCurrency), rules, cancellationToken);
if (rate.BidAsk == null)
return BadRequest();
var flatRate = rate.BidAsk.Center;
return Ok(flatRate * toCurrencyAmount);
}
public bool IsTest { get; set; } = false;
}
}

View file

@ -114,7 +114,6 @@ namespace BTCPayServer.Controllers.GreenField
//we do not include the default payment method in this model and instead opt to set it in the stores/storeid/payment-methods endpoints
//blob
//we do not include DefaultCurrencyPairs,Spread, PreferredExchange, RateScripting, RateScript in this model and instead opt to set it in stores/storeid/rates endpoints
//we do not include ChangellySettings in this model and instead opt to set it in stores/storeid/changelly endpoints
//we do not include CoinSwitchSettings in this model and instead opt to set it in stores/storeid/coinswitch endpoints
//we do not include ExcludedPaymentMethods in this model and instead opt to set it in stores/storeid/payment-methods endpoints
//we do not include EmailSettings in this model and instead opt to set it in stores/storeid/email endpoints
@ -150,7 +149,6 @@ namespace BTCPayServer.Controllers.GreenField
//we do not include the default payment method in this model and instead opt to set it in the stores/storeid/payment-methods endpoints
//blob
//we do not include DefaultCurrencyPairs;Spread; PreferredExchange; RateScripting; RateScript in this model and instead opt to set it in stores/storeid/rates endpoints
//we do not include ChangellySettings in this model and instead opt to set it in stores/storeid/changelly endpoints
//we do not include CoinSwitchSettings in this model and instead opt to set it in stores/storeid/coinswitch endpoints
//we do not include ExcludedPaymentMethods in this model and instead opt to set it in stores/storeid/payment-methods endpoints
//we do not include EmailSettings in this model and instead opt to set it in stores/storeid/email endpoints

View file

@ -15,7 +15,6 @@ using BTCPayServer.HostedServices;
using BTCPayServer.Models;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Changelly;
using BTCPayServer.Payments.CoinSwitch;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Rating;
@ -467,10 +466,6 @@ namespace BTCPayServer.Controllers
var currency = invoice.Currency;
var accounting = paymentMethod.Calculate();
ChangellySettings changelly = (storeBlob.ChangellySettings != null && storeBlob.ChangellySettings.Enabled &&
storeBlob.ChangellySettings.IsConfigured())
? storeBlob.ChangellySettings
: null;
CoinSwitchSettings coinswitch = (storeBlob.CoinSwitchSettings != null && storeBlob.CoinSwitchSettings.Enabled &&
storeBlob.CoinSwitchSettings.IsConfigured())
@ -478,11 +473,6 @@ namespace BTCPayServer.Controllers
: null;
var changellyAmountDue = changelly != null
? (accounting.Due.ToDecimal(MoneyUnit.BTC) *
(1m + (changelly.AmountMarkupPercentage / 100m)))
: (decimal?)null;
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
var divisibility = _CurrencyNameTable.GetNumberFormatInfo(paymentMethod.GetId().CryptoCode, false)?.CurrencyDecimalDigits;
@ -521,9 +511,6 @@ namespace BTCPayServer.Controllers
#pragma warning restore CS0618 // Type or member is obsolete
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
ChangellyEnabled = changelly != null,
ChangellyMerchantId = changelly?.ChangellyMerchantId,
ChangellyAmountDue = changellyAmountDue,
CoinSwitchEnabled = coinswitch != null,
CoinSwitchAmountMarkupPercentage = coinswitch?.AmountMarkupPercentage ?? 0,
CoinSwitchMerchantId = coinswitch?.MerchantId,

View file

@ -1,97 +0,0 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments.Changelly;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
{
public partial class StoresController
{
[HttpGet]
[Route("{storeId}/changelly")]
public IActionResult UpdateChangellySettings(string storeId)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
UpdateChangellySettingsViewModel vm = new UpdateChangellySettingsViewModel();
SetExistingValues(store, vm);
return View(vm);
}
private void SetExistingValues(StoreData store, UpdateChangellySettingsViewModel vm)
{
var existing = store.GetStoreBlob().ChangellySettings;
if (existing == null)
return;
vm.ApiKey = existing.ApiKey;
vm.ApiSecret = existing.ApiSecret;
vm.ApiUrl = existing.ApiUrl;
vm.ChangellyMerchantId = existing.ChangellyMerchantId;
vm.Enabled = existing.Enabled;
vm.AmountMarkupPercentage = existing.AmountMarkupPercentage;
}
[HttpPost]
[Route("{storeId}/changelly")]
public async Task<IActionResult> UpdateChangellySettings(string storeId, UpdateChangellySettingsViewModel vm,
string command)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
if (vm.Enabled)
{
if (!ModelState.IsValid)
{
return View(vm);
}
}
var changellySettings = new ChangellySettings()
{
ApiKey = vm.ApiKey,
ApiSecret = vm.ApiSecret,
ApiUrl = vm.ApiUrl,
ChangellyMerchantId = vm.ChangellyMerchantId,
Enabled = vm.Enabled,
AmountMarkupPercentage = vm.AmountMarkupPercentage
};
switch (command)
{
case "save":
var storeBlob = store.GetStoreBlob();
storeBlob.ChangellySettings = changellySettings;
store.SetStoreBlob(storeBlob);
await _Repo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = "Changelly settings modified";
_changellyClientProvider.InvalidateClient(storeId);
return RedirectToAction(nameof(UpdateStore), new
{
storeId
});
case "test":
try
{
var client = new Changelly(_httpClientFactory.CreateClient(), changellySettings.ApiKey, changellySettings.ApiSecret,
changellySettings.ApiUrl);
var result = await client.GetCurrenciesFull();
TempData[WellKnownTempData.SuccessMessage] = "Test Successful";
return View(vm);
}
catch (Exception ex)
{
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
return View(vm);
}
default:
return View(vm);
}
}
}
}

View file

@ -12,7 +12,6 @@ using BTCPayServer.HostedServices;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Changelly;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Rating;
using BTCPayServer.Security;
@ -57,7 +56,6 @@ namespace BTCPayServer.Controllers
ExplorerClientProvider explorerProvider,
IFeeProviderFactory feeRateProvider,
LanguageService langService,
ChangellyClientProvider changellyClientProvider,
IWebHostEnvironment env, IHttpClientFactory httpClientFactory,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
SettingsRepository settingsRepository,
@ -71,7 +69,6 @@ namespace BTCPayServer.Controllers
_TokenRepository = tokenRepo;
_UserManager = userManager;
_LangService = langService;
_changellyClientProvider = changellyClientProvider;
_TokenController = tokenController;
_WalletProvider = walletProvider;
_Env = env;
@ -102,7 +99,6 @@ namespace BTCPayServer.Controllers
readonly TokenRepository _TokenRepository;
readonly UserManager<ApplicationUser> _UserManager;
private readonly LanguageService _LangService;
private readonly ChangellyClientProvider _changellyClientProvider;
readonly IWebHostEnvironment _Env;
private readonly IHttpClientFactory _httpClientFactory;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
@ -537,13 +533,6 @@ namespace BTCPayServer.Controllers
}
}
var changellyEnabled = storeBlob.ChangellySettings != null && storeBlob.ChangellySettings.Enabled;
vm.ThirdPartyPaymentMethods.Add(new StoreViewModel.AdditionalPaymentMethod()
{
Enabled = changellyEnabled,
Action = nameof(UpdateChangellySettings),
Provider = "Changelly"
});
var coinSwitchEnabled = storeBlob.CoinSwitchSettings != null && storeBlob.CoinSwitchSettings.Enabled;
vm.ThirdPartyPaymentMethods.Add(new StoreViewModel.AdditionalPaymentMethod()

View file

@ -7,7 +7,6 @@ using BTCPayServer.Client.JsonConverters;
using BTCPayServer.Client.Models;
using BTCPayServer.JsonConverters;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Changelly;
using BTCPayServer.Payments.CoinSwitch;
using BTCPayServer.Rating;
using BTCPayServer.Services.Mails;
@ -108,10 +107,8 @@ namespace BTCPayServer.Data
public bool AnyoneCanInvoice { get; set; }
public ChangellySettings ChangellySettings { get; set; }
public CoinSwitchSettings CoinSwitchSettings { get; set; }
string _LightningDescriptionTemplate;
public string LightningDescriptionTemplate
{

View file

@ -10,7 +10,6 @@ using BTCPayServer.Logging;
using BTCPayServer.PaymentRequest;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Changelly;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Security;
@ -218,8 +217,6 @@ namespace BTCPayServer.Hosting
services.AddSingleton<PaymentMethodHandlerDictionary>();
services.AddSingleton<ChangellyClientProvider>();
services.AddSingleton<NotificationManager>();
services.AddScoped<NotificationSender>();

View file

@ -62,12 +62,8 @@ namespace BTCPayServer.Models.InvoicingModels
public string PaymentMethodId { get; set; }
public string PaymentMethodName { get; set; }
public string CryptoImage { get; set; }
public bool ChangellyEnabled { get; set; }
public string StoreId { get; set; }
public string PeerInfo { get; set; }
public string ChangellyMerchantId { get; set; }
public decimal? ChangellyAmountDue { get; set; }
public bool CoinSwitchEnabled { get; set; }
public string CoinSwitchMode { get; set; }

View file

@ -1,24 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace BTCPayServer.Models.StoreViewModels
{
public class UpdateChangellySettingsViewModel
{
[Required] public string ApiKey { get; set; }
[Required] public string ApiSecret { get; set; }
[Required] public string ApiUrl { get; set; } = "https://api.changelly.com";
[Display(Name = "Optional, Changelly Merchant Id")]
public string ChangellyMerchantId { get; set; }
[Required]
[Range(0, 100)]
[Display(Name =
"Percentage to multiply amount requested at Changelly to avoid underpaid situations due to Changelly not guaranteeing rates. ")]
public decimal AmountMarkupPercentage { get; set; } = new decimal(2);
public bool Enabled { get; set; }
}
}

View file

@ -1,84 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Payments.Changelly.Models;
using Newtonsoft.Json.Linq;
using SshNet.Security.Cryptography;
namespace BTCPayServer.Payments.Changelly
{
public class Changelly
{
private readonly string _apisecret;
private readonly HttpClient _httpClient;
public Changelly(HttpClient httpClient, string apiKey, string apiSecret, string apiUrl)
{
_apisecret = apiSecret;
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri(apiUrl);
_httpClient.DefaultRequestHeaders.Add("api-key", apiKey);
}
private static string ToHexString(byte[] array)
{
var hex = new StringBuilder(array.Length * 2);
foreach (var b in array)
{
hex.AppendFormat(CultureInfo.InvariantCulture, "{0:x2}", b);
}
return hex.ToString();
}
private async Task<ChangellyResponse<T>> PostToApi<T>(string message)
{
using var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(_apisecret));
var hashMessage = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
var sign = ToHexString(hashMessage);
using var request = new HttpRequestMessage(HttpMethod.Post, "");
request.Headers.Add("sign", sign);
request.Content = new StringContent(message, Encoding.UTF8, "application/json");
var result = await _httpClient.SendAsync(request);
if (!result.IsSuccessStatusCode)
throw new ChangellyException(result.ReasonPhrase);
var content =
await result.Content.ReadAsStringAsync();
return JObject.Parse(content).ToObject<ChangellyResponse<T>>();
}
public virtual async Task<IEnumerable<CurrencyFull>> GetCurrenciesFull()
{
const string message = @"{
""jsonrpc"": ""2.0"",
""id"": 1,
""method"": ""getCurrenciesFull"",
""params"": []
}";
var result = await PostToApi<IEnumerable<CurrencyFull>>(message);
return result.Result;
}
public virtual async Task<decimal> GetExchangeAmount(string fromCurrency,
string toCurrency,
decimal amount)
{
var message =
$"{{\"id\": \"test\",\"jsonrpc\": \"2.0\",\"method\": \"getExchangeAmount\",\"params\":{{\"from\": \"{fromCurrency}\",\"to\": \"{toCurrency}\",\"amount\": \"{amount}\"}}}}";
var result = await PostToApi<string>(message);
return Convert.ToDecimal(result.Result, CultureInfo.InvariantCulture);
}
}
}

View file

@ -1,16 +0,0 @@
namespace BTCPayServer.Payments.Changelly
{
public static class ChangellyCalculationHelper
{
public static decimal ComputeBaseAmount(decimal baseRate, decimal toAmount)
{
return (1m / baseRate) * toAmount;
}
public static decimal ComputeCorrectAmount(decimal currentFromAmount, decimal currentAmount,
decimal expectedAmount)
{
return (currentFromAmount / currentAmount) * expectedAmount;
}
}
}

View file

@ -1,71 +0,0 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Services.Stores;
using NBitcoin;
namespace BTCPayServer.Payments.Changelly
{
public class ChangellyClientProvider
{
private readonly StoreRepository _storeRepository;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ConcurrentDictionary<string, Changelly> _clientCache =
new ConcurrentDictionary<string, Changelly>();
public ChangellyClientProvider(StoreRepository storeRepository, IHttpClientFactory httpClientFactory)
{
_storeRepository = storeRepository;
_httpClientFactory = httpClientFactory;
}
public void InvalidateClient(string storeId)
{
if (_clientCache.ContainsKey(storeId))
{
_clientCache.Remove(storeId, out var value);
}
}
public virtual async Task<Changelly> TryGetChangellyClient(string storeId, StoreData storeData = null)
{
if (_clientCache.ContainsKey(storeId))
{
return _clientCache[storeId];
}
if (storeData == null)
{
storeData = await _storeRepository.FindStore(storeId);
if (storeData == null)
{
throw new ChangellyException("Store not found");
}
}
var blob = storeData.GetStoreBlob();
var changellySettings = blob.ChangellySettings;
if (changellySettings == null || !changellySettings.IsConfigured())
{
throw new ChangellyException("Changelly not configured for this store");
}
if (!changellySettings.Enabled)
{
throw new ChangellyException("Changelly not enabled for this store");
}
var changelly = new Changelly(_httpClientFactory.CreateClient("Changelly"), changellySettings.ApiKey,
changellySettings.ApiSecret,
changellySettings.ApiUrl);
_clientCache.AddOrReplace(storeId, changelly);
return changelly;
}
}
}

View file

@ -1,11 +0,0 @@
using System;
namespace BTCPayServer.Payments.Changelly
{
public class ChangellyException : Exception
{
public ChangellyException(string message) : base(message)
{
}
}
}

View file

@ -1,20 +0,0 @@
namespace BTCPayServer.Payments.Changelly
{
public class ChangellySettings
{
public string ApiKey { get; set; }
public string ApiSecret { get; set; }
public string ApiUrl { get; set; }
public bool Enabled { get; set; }
public string ChangellyMerchantId { get; set; }
public decimal AmountMarkupPercentage { get; set; }
public bool IsConfigured()
{
return
!string.IsNullOrEmpty(ApiKey) ||
!string.IsNullOrEmpty(ApiSecret) ||
!string.IsNullOrEmpty(ApiUrl);
}
}
}

View file

@ -1,17 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Changelly.Models
{
public class ChangellyResponse<T>
{
[JsonProperty("jsonrpc")]
public string JsonRPC { get; set; }
[JsonProperty("id")]
public object Id { get; set; }
[JsonProperty("result")]
public T Result { get; set; }
[JsonProperty("error")]
public Error Error { get; set; }
}
}

View file

@ -1,18 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Changelly.Models
{
public class CurrencyFull
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("fullName")]
public string FullName { get; set; }
[JsonProperty("enabled")]
public bool Enable { get; set; }
[JsonProperty("payinConfirmations")]
public int PayInConfirmations { get; set; }
[JsonProperty("image")]
public string ImageLink { get; set; }
}
}

View file

@ -1,13 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Changelly.Models
{
public class Error
{
[JsonProperty("code")]
public int Code { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
}

View file

@ -83,7 +83,7 @@
</div>
</nav>
</div>
@if (Model.ChangellyEnabled || Model.CoinSwitchEnabled)
@if (Model.CoinSwitchEnabled)
{
<div id="altcoins" class="bp-view payment manual-flow" v-bind:class="{ 'active': currentTab == 'altcoins'}">
<nav v-if="srvModel.isLightning">
@ -102,17 +102,16 @@
</span>
</div>
<center>
@if (Model.CoinSwitchEnabled && Model.ChangellyEnabled)
@if (Model.CoinSwitchEnabled)
{
<template v-if="!selectedThirdPartyProcessor">
<button v-on:click="selectedThirdPartyProcessor = 'coinswitch'" class="action-button">{{$t("Pay with CoinSwitch")}}</button>
<button v-on:click="selectedThirdPartyProcessor = 'changelly'" class="action-button">{{$t("Pay with Changelly")}}</button>
</template>
}
@if (Model.CoinSwitchEnabled)
{
<coinswitch inline-template
v-if="!srvModel.changellyEnabled || selectedThirdPartyProcessor === 'coinswitch'"
v-if="selectedThirdPartyProcessor === 'coinswitch'"
:mode="srvModel.coinSwitchMode"
:merchant-id="srvModel.coinSwitchMerchantId"
:to-currency="srvModel.paymentMethodId"
@ -121,10 +120,6 @@
:to-currency-address="srvModel.btcAddress">
<div>
<a v-on:click="openDialog($event)" :href="url" class="action-button" v-show="url && !opened">{{$t("Pay with CoinSwitch")}}</a>
@if (Model.ChangellyEnabled)
{
<button v-show="!opened" v-on:click="$parent.selectedThirdPartyProcessor = 'changelly'" class="btn-link mt-2">{{$t("Pay with Changelly")}}</button>
}
<iframe
v-if="showInlineIFrame"
v-on:load="onLoadIframe"
@ -135,45 +130,6 @@
</div>
</coinswitch>
}
@if (Model.ChangellyEnabled)
{
<changelly inline-template
v-if="!srvModel.coinSwitchEnabled || selectedThirdPartyProcessor === 'changelly'"
:merchant-id="srvModel.changellyMerchantId"
:store-id="srvModel.storeId"
:to-currency="srvModel.paymentMethodId"
:to-currency-due="srvModel.changellyAmountDue"
:to-currency-address="srvModel.btcAddress">
<div class="changelly-component">
<div class="changelly-component-dropdown-holder" v-show="prettyDropdownInstance">
<select
v-model="selectedFromCurrency"
:disabled="isLoading"
v-on:change="onCurrencyChange($event)"
ref="changellyCurrenciesDropdown">
<option value="">{{$t("ConversionTab_CurrencyList_Select_Option")}}</option>
<option v-for="currency of currencies"
:data-prefix="'<img src=\''+currency.image+'\'/>'"
:value="currency.name">{{currency.fullName}}</option>
</select>
</div>
<a v-on:click="openDialog($event)" :href="url" class="action-button" v-show="url">{{$t("Pay with Changelly")}}</a>
@if (Model.CoinSwitchEnabled)
{
<button v-on:click="$parent.selectedThirdPartyProcessor = 'coinswitch'" class="btn-link mt-2">{{$t("Pay with CoinSwitch")}}</button>
}
<button class="retry-button" v-if="calculateError" v-on:click="retry('calculateAmount')">
{{$t("ConversionTab_CalculateAmount_Error")}}
</button>
<button class="retry-button" v-if="currenciesError" v-on:click="retry('loadCurrencies')">
{{$t("ConversionTab_LoadCurrencies_Error")}}
</button>
<div v-show="isLoading" class="general__spinner">
<partial name="Checkout-Spinner"/>
</div>
</div>
</changelly>
}
</center>
</nav>
</div>
@ -195,17 +151,8 @@
<div class="payment-tabs__tab" id="copy-tab" v-on:click="switchTab('copy')" v-bind:class="{ 'active': currentTab == 'copy'}" >
<span>{{$t("Copy")}}</span>
</div>
@if (Model.ChangellyEnabled || Model.CoinSwitchEnabled)
{
<div class="payment-tabs__tab" id="altcoins-tab" v-on:click="switchTab('altcoins')" v-bind:class="{ 'active': currentTab == 'altcoins'}" >
<span>{{$t("Conversion")}}</span>
</div>
<div id="tabsSlider" class="payment-tabs__slider three-tabs" v-bind:class="['slide-'+currentTab]"></div>
}
else
{
<div id="tabsSlider" class="payment-tabs__slider" v-bind:class="['slide-'+currentTab]"></div>
}
<div id="tabsSlider" class="payment-tabs__slider" v-bind:class="['slide-'+currentTab]"></div>
</div>
</script>
@ -216,7 +163,6 @@
template: "#bitcoin-lightning-method-checkout-template",
components: {
qrcode: VueQrcode,
changelly: ChangellyComponent,
coinswitch: CoinSwitchComponent
},
data: function() {

View file

@ -1,53 +0,0 @@
@using Microsoft.AspNetCore.Mvc.Rendering
@model UpdateChangellySettingsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePageAndTitle(StoreNavPages.Index, "Update Store Changelly Settings");
}
<partial name="_StatusMessage" />
<div class="row">
<div class="col-md-10">
<form method="post">
<div class="alert alert-warning" role="alert">
If you are enabling Changelly support, we advise that you configure the invoice expiration to a minimum of 30 minutes as it may take longer than the default 15 minutes to convert the funds.
</div>
<div class="form-group">
<label asp-for="ApiUrl"></label>
<input asp-for="ApiUrl" class="form-control"/>
<span asp-validation-for="ApiUrl" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ApiKey"></label>
<input asp-for="ApiKey" class="form-control"/>
<span asp-validation-for="ApiKey" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ApiSecret"></label>
<input asp-for="ApiSecret" class="form-control"/>
<span asp-validation-for="ApiSecret" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ChangellyMerchantId"></label>
<input asp-for="ChangellyMerchantId" class="form-control"/>
<span asp-validation-for="ChangellyMerchantId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="AmountMarkupPercentage"></label>
<input asp-for="AmountMarkupPercentage" class="form-control"/>
<span asp-validation-for="AmountMarkupPercentage" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Enabled"></label>
<input asp-for="Enabled" type="checkbox" class="form-check"/>
</div>
<button name="command" type="submit" value="save" class="btn btn-primary">Submit</button>
<button name="command" type="submit" value="test" class="btn btn-primary">Test Settings</button>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}

View file

@ -11510,46 +11510,7 @@ low-fee-timeline {
opacity: 1;
transition: opacity 1s ease;
}
.changelly-component {
position: relative;
}
.changelly-component-dropdown-holder {
height: 32px;
margin-bottom: 10px;
}
.changelly-component .general__spinner bp-spinner {
width: 50px;
height: 50px;
}
.changelly-component .general__spinner bp-spinner svg {
margin: 0;
}
.changelly-component .retry-button {
border-radius: 5px;
border: 1px solid #a9a9a9;
background: #fff;
color: #000;
min-width: 100px;
padding-left: 0.8rem;
padding-right: 0.8rem;
min-height: 30px;
word-break: break-all;
}
.changelly-component .retry-button :hover {
border-color: #7f7f7f;
}
.changelly-component .prettydropdown li img {
width: 20px;
margin-right: 5px;
}
#prettydropdown-DefaultLang {
min-width: 200px;

View file

@ -1,131 +0,0 @@
var ChangellyComponent =
{
props: ["storeId", "toCurrency", "toCurrencyDue", "toCurrencyAddress", "merchantId"],
data: function () {
return {
currencies: [],
isLoading: true,
calculatedAmount: 0,
selectedFromCurrency: "",
prettyDropdownInstance: null,
calculateError: false,
currenciesError: false
};
},
computed: {
url: function () {
if (this.calculatedAmount && this.selectedFromCurrency && !this.isLoading) {
return "https://changelly.com/widget/v1?auth=email" +
"&from=" +
this.selectedFromCurrency +
"&to=" +
this.toCurrency +
"&address=" +
this.toCurrencyAddress +
"&amount=" +
this.calculatedAmount +
(this.merchantId ? "&merchant_id=" + this.merchantId + "&ref_id=" + this.merchantId : "");
}
return null;
}
},
watch: {
selectedFromCurrency: function (val) {
if (val) {
this.calculateAmount();
} else {
this.calculateAmount = 0;
}
}
},
mounted: function () {
this.prettyDropdownInstance = initDropdown(this.$refs.changellyCurrenciesDropdown);
this.loadCurrencies();
},
methods: {
getUrl: function () {
return window.location.origin + "/changelly/" + this.storeId;
},
loadCurrencies: function () {
this.isLoading = true;
this.currenciesError = false;
$.ajax(
{
context: this,
url: this.getUrl() + "/currencies",
dataType: "json",
success: function (result) {
for (i = 0; i < result.length; i++) {
if (result[i].enabled &&
result[i].name.toLowerCase() !== this.toCurrency.toLowerCase()) {
this.currencies.push(result[i]);
}
}
var self = this;
Vue.nextTick(function () {
self.prettyDropdownInstance
.refresh()
.on("change",
function (event) {
self.onCurrencyChange(self.$refs.changellyCurrenciesDropdown.value);
});
});
},
error: function(){
this.currenciesError = true;
},
complete: function () {
this.isLoading = false;
}
});
},
calculateAmount: function () {
this.isLoading = true;
this.calculateError = false;
$.ajax(
{
url: this.getUrl() + "/calculate",
dataType: "json",
data: {
fromCurrency: this.selectedFromCurrency,
toCurrency: this.toCurrency,
toCurrencyAmount: this.toCurrencyDue
},
context: this,
success: function (result) {
this.calculatedAmount = result;
},
error: function(){
this.calculateError = true;
},
complete: function () {
this.isLoading = false;
}
});
},
retry: function(type){
if(type=="loadCurrencies"){
this.loadCurrencies();
}else if(type=="calculateAmount"){
this.calculateAmount();
}
},
onCurrencyChange: function (value) {
this.selectedFromCurrency = value;
this.calculatedAmount = 0;
},
openDialog: function (e) {
if (e && e.preventDefault) {
e.preventDefault();
}
var changellyWindow = window.open(
this.url,
'Changelly',
'width=600,height=470,toolbar=0,menubar=0,location=0,status=1,scrollbars=1,resizable=0,left=0,top=0');
changellyWindow.focus();
}
}
};