mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
@@ -1,292 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using BTCPayServer.Controllers;
|
|
||||||
using BTCPayServer.Models;
|
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
|
||||||
using BTCPayServer.Payments.Changelly;
|
|
||||||
using BTCPayServer.Services.Stores;
|
|
||||||
using BTCPayServer.Tests.Logging;
|
|
||||||
using Changelly.ResponseModel;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
|
||||||
{
|
|
||||||
public class ChangellyTests
|
|
||||||
{
|
|
||||||
public ChangellyTests(ITestOutputHelper helper)
|
|
||||||
{
|
|
||||||
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
|
|
||||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async void CanSetChangellyPaymentMethod()
|
|
||||||
{
|
|
||||||
using (var tester = ServerTester.Create())
|
|
||||||
{
|
|
||||||
tester.Start();
|
|
||||||
var user = tester.NewAccount();
|
|
||||||
user.GrantAccess();
|
|
||||||
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
|
|
||||||
|
|
||||||
|
|
||||||
var storeBlob = controller.StoreData.GetStoreBlob();
|
|
||||||
Assert.Null(storeBlob.ChangellySettings);
|
|
||||||
|
|
||||||
var updateModel = new UpdateChangellySettingsViewModel()
|
|
||||||
{
|
|
||||||
ApiSecret = "secret",
|
|
||||||
ApiKey = "key",
|
|
||||||
ApiUrl = "url",
|
|
||||||
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.StoreData.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]
|
|
||||||
public async void CanToggleChangellyPaymentMethod()
|
|
||||||
{
|
|
||||||
using (var tester = ServerTester.Create())
|
|
||||||
{
|
|
||||||
tester.Start();
|
|
||||||
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 = "url",
|
|
||||||
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);
|
|
||||||
|
|
||||||
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]
|
|
||||||
public async void CannotUseChangellyApiWithoutChangellyPaymentMethodSet()
|
|
||||||
{
|
|
||||||
using (var tester = ServerTester.Create())
|
|
||||||
{
|
|
||||||
tester.Start();
|
|
||||||
var user = tester.NewAccount();
|
|
||||||
user.GrantAccess();
|
|
||||||
var changellyController =
|
|
||||||
tester.PayTester.GetController<ChangellyController>(user.UserId, user.StoreId);
|
|
||||||
|
|
||||||
//test non existing payment method
|
|
||||||
Assert.IsType<BitpayErrorModel>(Assert
|
|
||||||
.IsType<BadRequestObjectResult>(await changellyController.GetCurrencyList(user.StoreId))
|
|
||||||
.Value);
|
|
||||||
|
|
||||||
var updateModel = new UpdateChangellySettingsViewModel()
|
|
||||||
{
|
|
||||||
ApiSecret = "secret",
|
|
||||||
ApiKey = "key",
|
|
||||||
ApiUrl = "url",
|
|
||||||
ChangellyMerchantId = "aaa",
|
|
||||||
Enabled = 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<BadRequestObjectResult>(await changellyController.GetCurrencyList(user.StoreId))
|
|
||||||
.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async void CanGetCurrencyListFromChangelly()
|
|
||||||
{
|
|
||||||
using (var tester = ServerTester.Create())
|
|
||||||
{
|
|
||||||
tester.Start();
|
|
||||||
var user = tester.NewAccount();
|
|
||||||
user.GrantAccess();
|
|
||||||
|
|
||||||
var updateModel = new UpdateChangellySettingsViewModel()
|
|
||||||
{
|
|
||||||
ApiSecret = "secret",
|
|
||||||
ApiKey = "key",
|
|
||||||
ApiUrl = "url",
|
|
||||||
ChangellyMerchantId = "aaa"
|
|
||||||
};
|
|
||||||
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 mock = new MockChangellyClientProvider(tester.PayTester.StoreRepository, tester.NetworkProvider);
|
|
||||||
var changellyController = new ChangellyController(mock);
|
|
||||||
|
|
||||||
mock.GetCurrenciesFullResult = (new List<CurrencyFull>()
|
|
||||||
{
|
|
||||||
new CurrencyFull()
|
|
||||||
{
|
|
||||||
Name = "a",
|
|
||||||
Enable = true,
|
|
||||||
PayInConfirmations = 10,
|
|
||||||
FullName = "aa",
|
|
||||||
ImageLink = ""
|
|
||||||
}
|
|
||||||
}, true, "");
|
|
||||||
var result = ((IList<CurrencyFull> currency, bool Success, string Error))Assert
|
|
||||||
.IsType<OkObjectResult>(await changellyController.GetCurrencyList(user.StoreId)).Value;
|
|
||||||
Assert.Equal(1, mock.GetCurrenciesFullCallCount);
|
|
||||||
Assert.Equal(mock.GetCurrenciesFullResult.currency.Count, result.currency.Count);
|
|
||||||
|
|
||||||
mock.GetCurrenciesFullResult = (new List<CurrencyFull>()
|
|
||||||
{
|
|
||||||
new CurrencyFull()
|
|
||||||
{
|
|
||||||
Name = "a",
|
|
||||||
Enable = true,
|
|
||||||
PayInConfirmations = 10,
|
|
||||||
FullName = "aa",
|
|
||||||
ImageLink = ""
|
|
||||||
}
|
|
||||||
}, false, "");
|
|
||||||
Assert
|
|
||||||
.IsType<BadRequestObjectResult>(await changellyController.GetCurrencyList(user.StoreId));
|
|
||||||
Assert.Equal(2, mock.GetCurrenciesFullCallCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async void CanCalculateToAmountForChangelly()
|
|
||||||
{
|
|
||||||
using (var tester = ServerTester.Create())
|
|
||||||
{
|
|
||||||
tester.Start();
|
|
||||||
var user = tester.NewAccount();
|
|
||||||
user.GrantAccess();
|
|
||||||
|
|
||||||
var updateModel = new UpdateChangellySettingsViewModel()
|
|
||||||
{
|
|
||||||
ApiSecret = "secret",
|
|
||||||
ApiKey = "key",
|
|
||||||
ApiUrl = "url",
|
|
||||||
ChangellyMerchantId = "aaa"
|
|
||||||
};
|
|
||||||
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 mock = new MockChangellyClientProvider(tester.PayTester.StoreRepository, tester.NetworkProvider);
|
|
||||||
var changellyController = new ChangellyController(mock);
|
|
||||||
|
|
||||||
mock.GetExchangeAmountResult = (from, to, amount) =>
|
|
||||||
{
|
|
||||||
Assert.Equal("A", from);
|
|
||||||
Assert.Equal("B", to);
|
|
||||||
|
|
||||||
switch (mock.GetExchangeAmountCallCount)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
return (0.5, true, null);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return (1.01, true, null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Assert.IsType<double>(Assert
|
|
||||||
.IsType<OkObjectResult>(changellyController.CalculateAmount(user.StoreId, "A", "B", 1.0)).Value);
|
|
||||||
Assert.True(mock.GetExchangeAmountCallCount > 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MockChangellyClientProvider : ChangellyClientProvider
|
|
||||||
{
|
|
||||||
public MockChangellyClientProvider(
|
|
||||||
StoreRepository storeRepository,
|
|
||||||
BTCPayNetworkProvider btcPayNetworkProvider) : base(storeRepository, btcPayNetworkProvider)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public (IList<CurrencyFull> currency, bool Success, string Error) GetCurrenciesFullResult { get; set; }
|
|
||||||
|
|
||||||
public delegate TResult ParamsFunc<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3);
|
|
||||||
|
|
||||||
public ParamsFunc<string, string, double, (double amount, bool Success, string Error)> GetExchangeAmountResult
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetCurrenciesFullCallCount { get; set; } = 0;
|
|
||||||
public int GetExchangeAmountCallCount { get; set; } = 0;
|
|
||||||
|
|
||||||
public override (IList<CurrencyFull> currency, bool Success, string Error) GetCurrenciesFull(
|
|
||||||
Changelly.Changelly client)
|
|
||||||
{
|
|
||||||
GetCurrenciesFullCallCount++;
|
|
||||||
return GetCurrenciesFullResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (double amount, bool Success, string Error) GetExchangeAmount(Changelly.Changelly client,
|
|
||||||
string fromCurrency, string toCurrency,
|
|
||||||
double amount)
|
|
||||||
{
|
|
||||||
GetExchangeAmountCallCount++;
|
|
||||||
return GetExchangeAmountResult.Invoke(fromCurrency, toCurrency, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.1" />
|
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.1" />
|
||||||
<PackageReference Include="BuildBundlerMinifier" Version="2.7.385" />
|
<PackageReference Include="BuildBundlerMinifier" Version="2.7.385" />
|
||||||
<PackageReference Include="Changelly" Version="1.1.0" />
|
|
||||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.5.3" />
|
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.5.3" />
|
||||||
<PackageReference Include="Hangfire" Version="1.6.19" />
|
<PackageReference Include="Hangfire" Version="1.6.19" />
|
||||||
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.2" />
|
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.2" />
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BTCPayServer.Models;
|
|
||||||
using BTCPayServer.Payments.Changelly;
|
|
||||||
using Changelly.ResponseModel;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
|
||||||
{
|
|
||||||
[Route("[controller]/{storeId}")]
|
|
||||||
public class ChangellyController : Controller
|
|
||||||
{
|
|
||||||
private readonly ChangellyClientProvider _changellyClientProvider;
|
|
||||||
|
|
||||||
public ChangellyController(ChangellyClientProvider changellyClientProvider)
|
|
||||||
{
|
|
||||||
_changellyClientProvider = changellyClientProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route("currencies")]
|
|
||||||
public async Task<IActionResult> GetCurrencyList(string storeId)
|
|
||||||
{
|
|
||||||
if (!TryGetChangellyClient(storeId, out var actionResult, out var client))
|
|
||||||
{
|
|
||||||
return actionResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = _changellyClientProvider.GetCurrenciesFull(client);
|
|
||||||
if (result.Success)
|
|
||||||
{
|
|
||||||
return Ok(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return BadRequest(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route("calculate")]
|
|
||||||
public IActionResult CalculateAmount(string storeId, string fromCurrency, string toCurrency,
|
|
||||||
double toCurrencyAmount)
|
|
||||||
{
|
|
||||||
if (!TryGetChangellyClient(storeId, out var actionResult, out var client))
|
|
||||||
{
|
|
||||||
return actionResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
double? currentAmount = null;
|
|
||||||
var callCounter = 0;
|
|
||||||
|
|
||||||
var response1 = _changellyClientProvider.GetExchangeAmount(client,fromCurrency, toCurrency, 1);
|
|
||||||
if (!response1.Success) return BadRequest(response1);
|
|
||||||
currentAmount = response1.amount;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (callCounter > 10)
|
|
||||||
{
|
|
||||||
BadRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Client needs to be reset between same calls for some reason
|
|
||||||
if (!TryGetChangellyClient(storeId, out actionResult, out client))
|
|
||||||
{
|
|
||||||
return actionResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
var response2 = _changellyClientProvider.GetExchangeAmount(client,fromCurrency, toCurrency, currentAmount.Value);
|
|
||||||
callCounter++;
|
|
||||||
if (!response2.Success) return BadRequest(response2);
|
|
||||||
if (response2.amount < toCurrencyAmount)
|
|
||||||
{
|
|
||||||
var newCurrentAmount = ((toCurrencyAmount / response2.amount) * 1) * currentAmount.Value;
|
|
||||||
currentAmount = newCurrentAmount;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Ok(currentAmount.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetChangellyClient(string storeId, out IActionResult actionResult,
|
|
||||||
out Changelly.Changelly changelly)
|
|
||||||
{
|
|
||||||
changelly = null;
|
|
||||||
actionResult = null;
|
|
||||||
storeId = storeId ?? HttpContext.GetStoreData()?.Id;
|
|
||||||
|
|
||||||
if (!_changellyClientProvider.TryGetChangellyClient(storeId, out var error, out changelly))
|
|
||||||
{
|
|
||||||
actionResult = BadRequest(new BitpayErrorModel()
|
|
||||||
{
|
|
||||||
Error = error
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ using BTCPayServer.Events;
|
|||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
using BTCPayServer.Models.InvoicingModels;
|
using BTCPayServer.Models.InvoicingModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Changelly;
|
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
@@ -214,6 +213,7 @@ namespace BTCPayServer.Controllers
|
|||||||
paymentMethodIdStr = store.GetDefaultCrypto(_NetworkProvider);
|
paymentMethodIdStr = store.GetDefaultCrypto(_NetworkProvider);
|
||||||
isDefaultCrypto = true;
|
isDefaultCrypto = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var paymentMethodId = PaymentMethodId.Parse(paymentMethodIdStr);
|
var paymentMethodId = PaymentMethodId.Parse(paymentMethodIdStr);
|
||||||
var network = _NetworkProvider.GetNetwork(paymentMethodId.CryptoCode);
|
var network = _NetworkProvider.GetNetwork(paymentMethodId.CryptoCode);
|
||||||
if (network == null && isDefaultCrypto)
|
if (network == null && isDefaultCrypto)
|
||||||
@@ -245,18 +245,6 @@ namespace BTCPayServer.Controllers
|
|||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
var currency = invoice.ProductInformation.Currency;
|
var currency = invoice.ProductInformation.Currency;
|
||||||
var accounting = paymentMethod.Calculate();
|
var accounting = paymentMethod.Calculate();
|
||||||
|
|
||||||
ChangellySettings changelly = (storeBlob.ChangellySettings != null && storeBlob.ChangellySettings.Enabled &&
|
|
||||||
storeBlob.ChangellySettings.IsConfigured())
|
|
||||||
? storeBlob.ChangellySettings
|
|
||||||
: null;
|
|
||||||
|
|
||||||
|
|
||||||
var changellyAmountDue = changelly != null
|
|
||||||
? (accounting.Due.ToDecimal(MoneyUnit.BTC) *
|
|
||||||
(1m + (changelly.AmountMarkupPercentage / 100m)))
|
|
||||||
: (decimal?)null;
|
|
||||||
|
|
||||||
var model = new PaymentModel()
|
var model = new PaymentModel()
|
||||||
{
|
{
|
||||||
CryptoCode = network.CryptoCode,
|
CryptoCode = network.CryptoCode,
|
||||||
@@ -296,10 +284,7 @@ namespace BTCPayServer.Controllers
|
|||||||
Status = invoice.Status,
|
Status = invoice.Status,
|
||||||
NetworkFee = paymentMethodDetails.GetTxFee(),
|
NetworkFee = paymentMethodDetails.GetTxFee(),
|
||||||
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
|
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
|
||||||
ChangellyEnabled = changelly != null,
|
AllowCoinConversion = storeBlob.AllowCoinConversion,
|
||||||
ChangellyMerchantId = changelly?.ChangellyMerchantId,
|
|
||||||
ChangellyAmountDue = changellyAmountDue,
|
|
||||||
StoreId = store.Id,
|
|
||||||
AvailableCryptos = invoice.GetPaymentMethods(_NetworkProvider)
|
AvailableCryptos = invoice.GetPaymentMethods(_NetworkProvider)
|
||||||
.Where(i => i.Network != null)
|
.Where(i => i.Network != null)
|
||||||
.Select(kv => new PaymentModel.AvailableCrypto()
|
.Select(kv => new PaymentModel.AvailableCrypto()
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
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);
|
|
||||||
StatusMessage = "Changelly settings modified";
|
|
||||||
return RedirectToAction(nameof(UpdateStore), new {
|
|
||||||
storeId});
|
|
||||||
case "test":
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var client = new Changelly.Changelly(changellySettings.ApiKey, changellySettings.ApiSecret,
|
|
||||||
changellySettings.ApiUrl);
|
|
||||||
var result = client.GetCurrenciesFull();
|
|
||||||
vm.StatusMessage = !result.Success
|
|
||||||
? $"Error: {result.Error}"
|
|
||||||
: "Test Successful";
|
|
||||||
return View(vm);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
vm.StatusMessage = $"Error: {ex.Message}";
|
|
||||||
return View(vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return View(vm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ using BTCPayServer.Data;
|
|||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Models.AppViewModels;
|
using BTCPayServer.Models.AppViewModels;
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
using BTCPayServer.Payments.Changelly;
|
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
@@ -319,6 +318,7 @@ namespace BTCPayServer.Controllers
|
|||||||
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
|
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
|
||||||
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
|
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
|
||||||
vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? "";
|
vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? "";
|
||||||
|
vm.AllowCoinConversion = storeBlob.AllowCoinConversion;
|
||||||
vm.RequiresRefundEmail = storeBlob.RequiresRefundEmail;
|
vm.RequiresRefundEmail = storeBlob.RequiresRefundEmail;
|
||||||
vm.CustomCSS = storeBlob.CustomCSS?.AbsoluteUri;
|
vm.CustomCSS = storeBlob.CustomCSS?.AbsoluteUri;
|
||||||
vm.CustomLogo = storeBlob.CustomLogo?.AbsoluteUri;
|
vm.CustomLogo = storeBlob.CustomLogo?.AbsoluteUri;
|
||||||
@@ -362,6 +362,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
blob.DefaultLang = model.DefaultLang;
|
blob.DefaultLang = model.DefaultLang;
|
||||||
|
blob.AllowCoinConversion = model.AllowCoinConversion;
|
||||||
blob.RequiresRefundEmail = model.RequiresRefundEmail;
|
blob.RequiresRefundEmail = model.RequiresRefundEmail;
|
||||||
blob.LightningMaxValue = lightningMaxValue;
|
blob.LightningMaxValue = lightningMaxValue;
|
||||||
blob.OnChainMinValue = onchainMinValue;
|
blob.OnChainMinValue = onchainMinValue;
|
||||||
@@ -446,15 +447,6 @@ namespace BTCPayServer.Controllers
|
|||||||
Enabled = !excludeFilters.Match(paymentId)
|
Enabled = !excludeFilters.Match(paymentId)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var changellyEnabled = storeBlob.ChangellySettings != null && storeBlob.ChangellySettings.Enabled;
|
|
||||||
vm.ThirdPartyPaymentMethods.Add(new StoreViewModel.ThirdPartyPaymentMethod()
|
|
||||||
{
|
|
||||||
Enabled = changellyEnabled,
|
|
||||||
Action = nameof(UpdateChangellySettings),
|
|
||||||
Provider = "Changelly"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ using BTCPayServer.JsonConverters;
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using BTCPayServer.Payments.Changelly;
|
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
|
|
||||||
@@ -262,6 +261,11 @@ namespace BTCPayServer.Data
|
|||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
public bool AllowCoinConversion
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
public bool RequiresRefundEmail { get; set; }
|
public bool RequiresRefundEmail { get; set; }
|
||||||
|
|
||||||
public string DefaultLang { get; set; }
|
public string DefaultLang { get; set; }
|
||||||
@@ -304,8 +308,6 @@ namespace BTCPayServer.Data
|
|||||||
|
|
||||||
public bool AnyoneCanInvoice { get; set; }
|
public bool AnyoneCanInvoice { get; set; }
|
||||||
|
|
||||||
public ChangellySettings ChangellySettings { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
string _LightningDescriptionTemplate;
|
string _LightningDescriptionTemplate;
|
||||||
public string LightningDescriptionTemplate
|
public string LightningDescriptionTemplate
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ using BTCPayServer.Logging;
|
|||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
using Meziantou.AspNetCore.BundleTagHelpers;
|
using Meziantou.AspNetCore.BundleTagHelpers;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using BTCPayServer.Payments.Changelly;
|
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using NBXplorer.DerivationStrategy;
|
using NBXplorer.DerivationStrategy;
|
||||||
@@ -127,8 +126,6 @@ namespace BTCPayServer.Hosting
|
|||||||
services.AddSingleton<Payments.IPaymentMethodHandler<Payments.Lightning.LightningSupportedPaymentMethod>, Payments.Lightning.LightningLikePaymentHandler>();
|
services.AddSingleton<Payments.IPaymentMethodHandler<Payments.Lightning.LightningSupportedPaymentMethod>, Payments.Lightning.LightningLikePaymentHandler>();
|
||||||
services.AddSingleton<IHostedService, Payments.Lightning.LightningListener>();
|
services.AddSingleton<IHostedService, Payments.Lightning.LightningListener>();
|
||||||
|
|
||||||
services.AddSingleton<ChangellyClientProvider>();
|
|
||||||
|
|
||||||
services.AddSingleton<IHostedService, NBXplorerWaiters>();
|
services.AddSingleton<IHostedService, NBXplorerWaiters>();
|
||||||
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
|
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
|
||||||
services.AddSingleton<IHostedService, InvoiceWatcher>();
|
services.AddSingleton<IHostedService, InvoiceWatcher>();
|
||||||
|
|||||||
@@ -55,10 +55,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
public string PaymentMethodName { get; set; }
|
public string PaymentMethodName { get; set; }
|
||||||
public string CryptoImage { get; set; }
|
public string CryptoImage { get; set; }
|
||||||
|
|
||||||
public bool ChangellyEnabled { get; set; }
|
public bool AllowCoinConversion { get; set; }
|
||||||
public string StoreId { get; set; }
|
|
||||||
public string PeerInfo { get; set; }
|
public string PeerInfo { get; set; }
|
||||||
public string ChangellyMerchantId { get; set; }
|
|
||||||
public decimal? ChangellyAmountDue { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
public string DefaultCryptoCurrency { get; set; }
|
public string DefaultCryptoCurrency { get; set; }
|
||||||
[Display(Name = "Default language on checkout")]
|
[Display(Name = "Default language on checkout")]
|
||||||
public string DefaultLang { get; set; }
|
public string DefaultLang { get; set; }
|
||||||
|
[Display(Name = "Allow conversion through third party (Shapeshift, Changelly...)")]
|
||||||
|
public bool AllowCoinConversion
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
[Display(Name = "Do not propose lightning payment if value of the invoice is above...")]
|
[Display(Name = "Do not propose lightning payment if value of the invoice is above...")]
|
||||||
[MaxLength(20)]
|
[MaxLength(20)]
|
||||||
public string LightningMaxValue { get; set; }
|
public string LightningMaxValue { get; set; }
|
||||||
|
|||||||
@@ -22,12 +22,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ThirdPartyPaymentMethod
|
|
||||||
{
|
|
||||||
public string Provider { get; set; }
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
public string Action { get; set; }
|
|
||||||
}
|
|
||||||
public StoreViewModel()
|
public StoreViewModel()
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -58,9 +52,6 @@ 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>();
|
||||||
|
|
||||||
public List<ThirdPartyPaymentMethod> ThirdPartyPaymentMethods { get; set; } =
|
|
||||||
new List<ThirdPartyPaymentMethod>();
|
|
||||||
|
|
||||||
[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 * 24)]
|
[Range(1, 60 * 24 * 24)]
|
||||||
public int InvoiceExpiration
|
public int InvoiceExpiration
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using BTCPayServer.Payments;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
||||||
|
|
||||||
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; } = "804298eb5753";
|
|
||||||
|
|
||||||
public bool Enabled { get; set; } = true;
|
|
||||||
|
|
||||||
public string StatusMessage { 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);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using BTCPayServer.Services.Stores;
|
|
||||||
using Changelly.ResponseModel;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Payments.Changelly
|
|
||||||
{
|
|
||||||
public class ChangellyClientProvider
|
|
||||||
{
|
|
||||||
private readonly StoreRepository _storeRepository;
|
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
|
||||||
|
|
||||||
|
|
||||||
public ChangellyClientProvider(StoreRepository storeRepository, BTCPayNetworkProvider btcPayNetworkProvider)
|
|
||||||
{
|
|
||||||
_storeRepository = storeRepository;
|
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public virtual bool TryGetChangellyClient(string storeId, out string error,
|
|
||||||
out global::Changelly.Changelly changelly)
|
|
||||||
{
|
|
||||||
changelly = null;
|
|
||||||
|
|
||||||
|
|
||||||
var store = _storeRepository.FindStore(storeId).Result;
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
error = "Store not found";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var blob = store.GetStoreBlob();
|
|
||||||
var changellySettings = blob.ChangellySettings;
|
|
||||||
|
|
||||||
|
|
||||||
if (changellySettings == null || !changellySettings.IsConfigured())
|
|
||||||
{
|
|
||||||
error = "Changelly not configured for this store";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!changellySettings.Enabled)
|
|
||||||
{
|
|
||||||
error = "Changelly not enabled for this store";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
changelly = new global::Changelly.Changelly(changellySettings.ApiKey, changellySettings.ApiSecret,
|
|
||||||
changellySettings.ApiUrl);
|
|
||||||
error = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual (IList<CurrencyFull> currency, bool Success, string Error) GetCurrenciesFull(global::Changelly.Changelly client)
|
|
||||||
{
|
|
||||||
return client.GetCurrenciesFull();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual (double amount, bool Success, string Error) GetExchangeAmount(global::Changelly.Changelly client, string fromCurrency, string toCurrency,
|
|
||||||
double amount)
|
|
||||||
{
|
|
||||||
|
|
||||||
return client.GetExchangeAmount(fromCurrency, toCurrency, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Payments.Changelly;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
<div class="payment-tabs__tab" id="copy-tab">
|
<div class="payment-tabs__tab" id="copy-tab">
|
||||||
<span>{{$t("Copy")}}</span>
|
<span>{{$t("Copy")}}</span>
|
||||||
</div>
|
</div>
|
||||||
@if (Model.ChangellyEnabled)
|
@if (Model.AllowCoinConversion)
|
||||||
{
|
{
|
||||||
<div class="payment-tabs__tab" id="altcoins-tab">
|
<div class="payment-tabs__tab" id="altcoins-tab">
|
||||||
<span>{{$t("Conversion")}}</span>
|
<span>{{$t("Conversion")}}</span>
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@if (Model.ChangellyEnabled)
|
@if (Model.AllowCoinConversion)
|
||||||
{
|
{
|
||||||
<div id="altcoins" class="bp-view payment manual-flow">
|
<div id="altcoins" class="bp-view payment manual-flow">
|
||||||
<nav v-if="srvModel.isLightning">
|
<nav v-if="srvModel.isLightning">
|
||||||
@@ -272,31 +272,16 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<center>
|
<center>
|
||||||
<changelly inline-template
|
<script>function shapeshift_click(a, e) { e.preventDefault(); var link = a.href; var shapeshiftWindow = window.open(link, '1418115287605', 'width=700,height=500,toolbar=0,menubar=0,location=0,status=1,scrollbars=1,resizable=0,left=0,top=0'); shapeshiftWindow.focus(); return false; }</script>
|
||||||
:merchant-id="srvModel.changellyMerchantId"
|
<a onclick="shapeshift_click(this, event);" v-bind:href="srvModel.shapeshiftUrl">
|
||||||
:store-id="srvModel.storeId"
|
<img src="https://shapeshift.io/images/shifty/xs_light_altcoins.png" class="ss-button">
|
||||||
:to-currency="srvModel.paymentMethodId"
|
</a>
|
||||||
:to-currency-due="srvModel.changellyAmountDue"
|
|
||||||
:to-currency-address="srvModel.btcAddress">
|
@*Changelly doesn't have TO_AMOUNT support so we can't include it
|
||||||
<div class="changelly-component">
|
<script type="text/javascript">function open_widget(a, e) { e.preventDefault(); var link = a.href; var changellyWindow = window.open(link, 'Changelly', 'width=600,height=470,toolbar=0,menubar=0,location=0,status=1,scrollbars=1,resizable=0,left=0,top=0'); changellyWindow.focus(); return false; }</script>
|
||||||
<div class="changelly-component-dropdown-holder" v-show="prettyDropdownInstance">
|
<a onclick="open_widget(this, event);" href="https://changelly.com/widget/v1?auth=email&from=DASH&to=BTC&address=&amount=1&merchant_id=&ref_id=">
|
||||||
<select
|
<img src="https://changelly.com/pay_button_pay_with.png" alt="Changelly" />
|
||||||
v-model="selectedFromCurrency"
|
</a>*@
|
||||||
:disabled="isLoading"
|
|
||||||
v-on:change="onCurrencyChange($event)"
|
|
||||||
ref="changellyCurrenciesDropdown">
|
|
||||||
<option value="">Select a currency to convert from</option>
|
|
||||||
<option v-for="currency of currencies" :value="currency.name">{{currency.fullName}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<a v-on:click="openDialog($event)" :href="url" class="changelly-component-button">
|
|
||||||
<img src="https://changelly.com/pay_button.png" alt="Changelly" v-show="url"/>
|
|
||||||
</a>
|
|
||||||
<div v-show="isLoading" class="general__spinner">
|
|
||||||
<partial name="Checkout-Spinner"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</changelly>
|
|
||||||
</center>
|
</center>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -53,53 +53,49 @@
|
|||||||
</center>
|
</center>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
|
|
||||||
<invoice>
|
<invoice>
|
||||||
<div class="no-bounce" id="checkoutCtrl" v-cloak>
|
<div class="no-bounce" id="checkoutCtrl" v-cloak>
|
||||||
<div class="modal page">
|
<div class="modal page">
|
||||||
<div class="modal-dialog open opened enter-purchaser-email" role="document">
|
<div class="modal-dialog open opened enter-purchaser-email" role="document">
|
||||||
<div class="modal-content long">
|
<div class="modal-content long">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="invoice">
|
<div class="invoice">
|
||||||
<partial name="Checkout-Body" />
|
<partial name="Checkout-Body" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 10px; text-align: center;">
|
<div style="margin-top: 10px; text-align: center;">
|
||||||
@* Not working because of nsSeparator: false, keySeparator: false,
|
@* Not working because of nsSeparator: false, keySeparator: false,
|
||||||
{{$t("nested.lang")}} >>
|
{{$t("nested.lang")}} >>
|
||||||
*@
|
*@
|
||||||
<select class="cmblang reverse invisible" onchange="changeLanguage($(this).val())">
|
<select class="cmblang reverse invisible" onchange="changeLanguage($(this).val())">
|
||||||
@foreach (var lang in langService.GetLanguages())
|
@foreach (var lang in langService.GetLanguages())
|
||||||
{
|
{
|
||||||
<option value="@lang.Code">@lang.DisplayName</option>
|
<option value="@lang.Code">@lang.DisplayName</option>
|
||||||
}
|
|
||||||
</select>
|
|
||||||
<script>
|
|
||||||
$(function() {
|
|
||||||
var storeDefaultLang = '@Model.DefaultLang';
|
|
||||||
if (urlParams.lang) {
|
|
||||||
$(".cmblang").val(urlParams.lang);
|
|
||||||
} else if (storeDefaultLang) {
|
|
||||||
$(".cmblang").val(storeDefaultLang);
|
|
||||||
}
|
}
|
||||||
|
</select>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
var storeDefaultLang = '@Model.DefaultLang';
|
||||||
|
if (urlParams.lang) {
|
||||||
|
$(".cmblang").val(urlParams.lang);
|
||||||
|
} else if (storeDefaultLang) {
|
||||||
|
$(".cmblang").val(storeDefaultLang);
|
||||||
|
}
|
||||||
|
|
||||||
// REVIEW: don't use initDropdown method but rather directly initialize select whenever you are using it
|
$('select').prettyDropdown({
|
||||||
initDropdown(".cmblang");
|
classic: false,
|
||||||
});
|
height: 32,
|
||||||
|
reverse: true,
|
||||||
function initDropdown(selector) {
|
hoverIntent: 5000
|
||||||
return $(selector).prettyDropdown({
|
});
|
||||||
classic: false,
|
|
||||||
height: 32,
|
|
||||||
reverse: true,
|
|
||||||
hoverIntent: 5000
|
|
||||||
});
|
});
|
||||||
}
|
</script>
|
||||||
</script>
|
</div>
|
||||||
</div>
|
<div style="margin-top: 10px; text-align: center;" class="form-text small text-muted">
|
||||||
<div style="margin-top: 10px; text-align: center;" class="form-text small text-muted">
|
<span>Powered by <a target="_blank" href="https://github.com/btcpayserver/btcpayserver">BTCPay Server</a></span>
|
||||||
<span>Powered by <a target="_blank" href="https://github.com/btcpayserver/btcpayserver">BTCPay Server</a></span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,40 +127,39 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function changeLanguage(lang) {
|
function changeLanguage(lang) {
|
||||||
i18next.changeLanguage(lang);
|
i18next.changeLanguage(lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlParams.lang) {
|
if (urlParams.lang) {
|
||||||
changeLanguage(urlParams.lang);
|
changeLanguage(urlParams.lang);
|
||||||
} else if (storeDefaultLang) {
|
}
|
||||||
changeLanguage(storeDefaultLang);
|
else if (storeDefaultLang) {
|
||||||
}
|
changeLanguage(storeDefaultLang);
|
||||||
|
}
|
||||||
|
|
||||||
const i18n = new VueI18next(i18next);
|
const i18n = new VueI18next(i18next);
|
||||||
|
|
||||||
// TODO: Move all logic from core.js to Vue controller
|
// TODO: Move all logic from core.js to Vue controller
|
||||||
Vue.config.ignoredElements = [
|
Vue.config.ignoredElements = [
|
||||||
'line-items',
|
'line-items',
|
||||||
'low-fee-timeline',
|
'low-fee-timeline',
|
||||||
// Ignoring custom HTML5 elements, eg: bp-spinner
|
// Ignoring custom HTML5 elements, eg: bp-spinner
|
||||||
/^bp-/
|
/^bp-/
|
||||||
];
|
];
|
||||||
|
var checkoutCtrl = new Vue({
|
||||||
var checkoutCtrl = new Vue({
|
i18n: i18n,
|
||||||
i18n: i18n,
|
el: '#checkoutCtrl',
|
||||||
el: '#checkoutCtrl',
|
components: {
|
||||||
components: {
|
qrcode: VueQr
|
||||||
qrcode: VueQr,
|
},
|
||||||
changelly: ChangellyComponent
|
data: {
|
||||||
},
|
|
||||||
data: {
|
|
||||||
srvModel: srvModel,
|
srvModel: srvModel,
|
||||||
lndModel: null,
|
lndModel: null,
|
||||||
scanDisplayQr: "",
|
scanDisplayQr: "",
|
||||||
expiringSoon: false
|
expiringSoon: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -38,6 +38,10 @@
|
|||||||
<label asp-for="DefaultLang"></label>
|
<label asp-for="DefaultLang"></label>
|
||||||
<select asp-for="DefaultLang" asp-items="Model.Languages" class="form-control"></select>
|
<select asp-for="DefaultLang" asp-items="Model.Languages" class="form-control"></select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="AllowCoinConversion"></label>
|
||||||
|
<input asp-for="AllowCoinConversion" type="checkbox" class="form-check" />
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="RequiresRefundEmail"></label>
|
<label asp-for="RequiresRefundEmail"></label>
|
||||||
<input asp-for="RequiresRefundEmail" type="checkbox" class="form-check" />
|
<input asp-for="RequiresRefundEmail" type="checkbox" class="form-check" />
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
@using Microsoft.AspNetCore.Mvc.Rendering
|
|
||||||
@model UpdateChangellySettingsViewModel
|
|
||||||
@{
|
|
||||||
Layout = "../Shared/_NavLayout.cshtml";
|
|
||||||
ViewData.SetActivePageAndTitle(StoreNavPages.Index, "Update Store Changelly Settings");
|
|
||||||
}
|
|
||||||
|
|
||||||
<h4>@ViewData["Title"]</h4>
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage"/>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-10">
|
|
||||||
<form method="post">
|
|
||||||
<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")
|
|
||||||
}
|
|
||||||
@@ -168,43 +168,6 @@
|
|||||||
Available placeholders are: {StoreName}, {ItemDescription} and {OrderId}
|
Available placeholders are: {StoreName}, {ItemDescription} and {OrderId}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-group">
|
|
||||||
<h5>Third party Payment methods</h5>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<table class="table table-sm table-responsive-md">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Provider</th>
|
|
||||||
<th style="text-align:center;">Enabled</th>
|
|
||||||
<th style="text-align:right">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach(var scheme in Model.ThirdPartyPaymentMethods)
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>@scheme.Provider</td>
|
|
||||||
<td style="text-align:center;">
|
|
||||||
@if(scheme.Enabled)
|
|
||||||
{
|
|
||||||
<span class="fa fa-check"></span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="fa fa-times"></span>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td style="text-align:right"><a asp-action="@scheme.Action" >Modify</a></td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if(Model.CanDelete)
|
@if(Model.CanDelete)
|
||||||
{
|
{
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -9390,7 +9390,7 @@ strong {
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: #000;
|
color: #000;
|
||||||
font-size: 0px;
|
font-size: 0px;
|
||||||
width: 100%;
|
width:100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btnGroupLnd button:first-child {
|
.btnGroupLnd button:first-child {
|
||||||
@@ -10995,15 +10995,6 @@ bp-spinner {
|
|||||||
opacity: .85;
|
opacity: .85;
|
||||||
}
|
}
|
||||||
|
|
||||||
.general__spinner > bp-spinner > svg {
|
|
||||||
margin: auto 0px 0px auto;
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
fill: gray;
|
|
||||||
animation: spin 0.55s linear infinite;
|
|
||||||
opacity: .85;
|
|
||||||
}
|
|
||||||
|
|
||||||
bp-refund-address.ng-valid .bitcoin-logo {
|
bp-refund-address.ng-valid .bitcoin-logo {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
@@ -11468,16 +11459,3 @@ low-fee-timeline {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: opacity 1s ease;
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
var ChangellyComponent =
|
|
||||||
{
|
|
||||||
props: ["storeId", "toCurrency", "toCurrencyDue", "toCurrencyAddress", "merchantId"],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
currencies: [],
|
|
||||||
isLoading: true,
|
|
||||||
calculatedAmount: 0,
|
|
||||||
selectedFromCurrency: "",
|
|
||||||
prettyDropdownInstance: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
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;
|
|
||||||
$.ajax(
|
|
||||||
{
|
|
||||||
context: this,
|
|
||||||
url: this.getUrl() + "/currencies",
|
|
||||||
dataType: "json",
|
|
||||||
success: function (result) {
|
|
||||||
if (result.item2) {
|
|
||||||
for (i = 0; i < result.item1.length; i++) {
|
|
||||||
if (result.item1[i].enabled &&
|
|
||||||
result.item1[i].name.toLowerCase() !== this.toCurrency.toLowerCase()) {
|
|
||||||
this.currencies.push(result.item1[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var self = this;
|
|
||||||
Vue.nextTick(function () {
|
|
||||||
self.prettyDropdownInstance
|
|
||||||
.refresh()
|
|
||||||
.on("change",
|
|
||||||
function (event) {
|
|
||||||
self.onCurrencyChange(self.$refs.changellyCurrenciesDropdown.value);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
complete: function () {
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
calculateAmount: function () {
|
|
||||||
this.isLoading = true;
|
|
||||||
$.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;
|
|
||||||
},
|
|
||||||
complete: function () {
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
onCurrencyChange: function (value) {
|
|
||||||
this.selectedFromCurrency = value;
|
|
||||||
},
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -32,6 +32,9 @@ function changeCurrency(currency) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onDataCallback(jsonData) {
|
function onDataCallback(jsonData) {
|
||||||
|
// extender properties used
|
||||||
|
jsonData.shapeshiftUrl = "https://shapeshift.io/shifty.html?destination=" + jsonData.btcAddress + "&output=" + jsonData.paymentMethodId + "&amount=" + jsonData.btcDue;
|
||||||
|
//
|
||||||
|
|
||||||
var newStatus = jsonData.status;
|
var newStatus = jsonData.status;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user