initial coinswitch work

This commit is contained in:
Kukks 2018-12-11 12:47:38 +01:00
parent 94be2b46d5
commit c00c95efcf
13 changed files with 419 additions and 36 deletions

View file

@ -0,0 +1,89 @@
using BTCPayServer.Controllers;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments.CoinSwitch;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Xunit;
using Xunit.Abstractions;
namespace BTCPayServer.Tests
{
public class CoinSwitchTests
{
public CoinSwitchTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact]
[Trait("Integration", "Integration")]
public async void CanSetCoinSwitchPaymentMethod()
{
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.CoinSwitchSettings);
var updateModel = new UpdateCoinSwitchSettingsViewModel()
{
MerchantId = "aaa",
};
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
storeBlob = controller.StoreData.GetStoreBlob();
Assert.NotNull(storeBlob.CoinSwitchSettings);
Assert.NotNull(storeBlob.CoinSwitchSettings);
Assert.IsType<CoinSwitchSettings>(storeBlob.CoinSwitchSettings);
Assert.Equal(storeBlob.CoinSwitchSettings.MerchantId,
updateModel.MerchantId);
}
}
[Fact]
[Trait("Integration", "Integration")]
public async void CanToggleCoinSwitchPaymentMethod()
{
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 UpdateCoinSwitchSettingsViewModel()
{
MerchantId = "aaa",
Enabled = true
};
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
Assert.True(store.GetStoreBlob().CoinSwitchSettings.Enabled);
updateModel.Enabled = false;
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
Assert.False(store.GetStoreBlob().CoinSwitchSettings.Enabled);
}
}
}
}

View file

@ -13,6 +13,7 @@ using BTCPayServer.Models;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Changelly;
using BTCPayServer.Payments.CoinSwitch;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Security;
using BTCPayServer.Services.Invoices;
@ -258,6 +259,11 @@ namespace BTCPayServer.Controllers
storeBlob.ChangellySettings.IsConfigured())
? storeBlob.ChangellySettings
: null;
CoinSwitchSettings coinswitch = (storeBlob.CoinSwitchSettings != null && storeBlob.CoinSwitchSettings.Enabled &&
storeBlob.CoinSwitchSettings.IsConfigured())
? storeBlob.CoinSwitchSettings
: null;
var changellyAmountDue = changelly != null
@ -309,6 +315,8 @@ namespace BTCPayServer.Controllers
ChangellyEnabled = changelly != null,
ChangellyMerchantId = changelly?.ChangellyMerchantId,
ChangellyAmountDue = changellyAmountDue,
CoinSwitchEnabled = coinswitch != null,
CoinSwitchMerchantId = coinswitch?.MerchantId,
StoreId = store.Id,
AvailableCryptos = invoice.GetPaymentMethods(_NetworkProvider)
.Where(i => i.Network != null)

View file

@ -0,0 +1,70 @@
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments.CoinSwitch;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
{
public partial class StoresController
{
[HttpGet]
[Route("{storeId}/coinswitch")]
public IActionResult UpdateCoinSwitchSettings(string storeId)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
UpdateCoinSwitchSettingsViewModel vm = new UpdateCoinSwitchSettingsViewModel();
SetExistingValues(store, vm);
return View(vm);
}
private void SetExistingValues(StoreData store, UpdateCoinSwitchSettingsViewModel vm)
{
var existing = store.GetStoreBlob().CoinSwitchSettings;
if (existing == null) return;
vm.MerchantId = existing.MerchantId;
vm.Enabled = existing.Enabled;
}
[HttpPost]
[Route("{storeId}/coinswitch")]
public async Task<IActionResult> UpdateCoinSwitchSettings(string storeId, UpdateCoinSwitchSettingsViewModel vm,
string command)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
if (vm.Enabled)
{
if (!ModelState.IsValid)
{
return View(vm);
}
}
var coinSwitchSettings = new CoinSwitchSettings()
{
MerchantId = vm.MerchantId,
Enabled = vm.Enabled
};
switch (command)
{
case "save":
var storeBlob = store.GetStoreBlob();
storeBlob.CoinSwitchSettings = coinSwitchSettings;
store.SetStoreBlob(storeBlob);
await _Repo.UpdateStore(store);
StatusMessage = "CoinSwitch settings modified";
return RedirectToAction(nameof(UpdateStore), new {
storeId});
default:
return View(vm);
}
}
}
}

View file

@ -464,6 +464,14 @@ namespace BTCPayServer.Controllers
Action = nameof(UpdateChangellySettings),
Provider = "Changelly"
});
var coinSwitchEnabled = storeBlob.CoinSwitchSettings != null && storeBlob.CoinSwitchSettings.Enabled;
vm.ThirdPartyPaymentMethods.Add(new StoreViewModel.ThirdPartyPaymentMethod()
{
Enabled = coinSwitchEnabled,
Action = nameof(UpdateCoinSwitchSettings),
Provider = "CoinSwitch"
});
}
[HttpPost]

View file

@ -18,6 +18,7 @@ using System.ComponentModel.DataAnnotations;
using BTCPayServer.Services;
using System.Security.Claims;
using BTCPayServer.Payments.Changelly;
using BTCPayServer.Payments.CoinSwitch;
using BTCPayServer.Security;
using BTCPayServer.Rating;
@ -305,6 +306,7 @@ namespace BTCPayServer.Data
public bool AnyoneCanInvoice { get; set; }
public ChangellySettings ChangellySettings { get; set; }
public CoinSwitchSettings CoinSwitchSettings { get; set; }
string _LightningDescriptionTemplate;

View file

@ -61,5 +61,8 @@ namespace BTCPayServer.Models.InvoicingModels
public string PeerInfo { get; set; }
public string ChangellyMerchantId { get; set; }
public decimal? ChangellyAmountDue { get; set; }
public bool CoinSwitchEnabled { get; set; }
public string CoinSwitchMerchantId { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using BTCPayServer.Payments.CoinSwitch;
namespace BTCPayServer.Models.StoreViewModels
{
public class UpdateCoinSwitchSettingsViewModel
{
public string MerchantId { get; set; }
public bool Enabled { get; set; }
public string StatusMessage { get; set; }
}
}

View file

@ -0,0 +1,15 @@
namespace BTCPayServer.Payments.CoinSwitch
{
public class CoinSwitchSettings
{
public string MerchantId { get; set; }
public bool Enabled { get; set; }
public bool IsConfigured()
{
return
!string.IsNullOrEmpty(MerchantId);
}
}
}

View file

@ -151,7 +151,7 @@
<div class="payment-tabs__tab" id="copy-tab">
<span>{{$t("Copy")}}</span>
</div>
@if (Model.ChangellyEnabled)
@if (Model.ChangellyEnabled || Model.CoinSwitchEnabled)
{
<div class="payment-tabs__tab" id="altcoins-tab">
<span>{{$t("Conversion")}}</span>
@ -256,7 +256,7 @@
</div>
</nav>
</div>
@if (Model.ChangellyEnabled)
@if (Model.ChangellyEnabled || Model.CoinSwitchEnabled)
{
<div id="altcoins" class="bp-view payment manual-flow">
<nav v-if="srvModel.isLightning">
@ -274,42 +274,74 @@
{{$t("ConversionTab_BodyDesc", srvModel)}}
</span>
</div>
<center>
<changelly inline-template
: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
<center>
@if (Model.CoinSwitchEnabled && Model.ChangellyEnabled)
{
<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'"
:merchant-id="srvModel.changellyMerchantId"
:to-currency="srvModel.paymentMethodId"
:to-currency-due="srvModel.btcDue"
:autoload="selectedThirdPartyProcessor === 'coinswitch'"
:to-currency-address="srvModel.btcAddress">
<a v-on:click="openDialog($event)" :href="url" class="action-button" v-show="url">
{{$t("Pay with CoinSwitch")}}
</a>
</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)"
v-on:change="onCurrencyChange($event)"payment__details__instruction__open-wallet__btn
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>
<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>
<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>
<a v-on:click="openDialog($event)" :href="url" class="btn btn-primary retry-button changelly-component-button" v-show="url">
Pay with Changelly
</a>
<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>
</changelly>
}
</center>
</nav>
</div>

View file

@ -170,14 +170,16 @@
el: '#checkoutCtrl',
components: {
qrcode: VueQr,
changelly: ChangellyComponent
changelly: ChangellyComponent,
coinswitch: CoinSwitchComponent
},
data: {
srvModel: srvModel,
lndModel: null,
scanDisplayQr: "",
expiringSoon: false,
isModal: srvModel.isModal
isModal: srvModel.isModal,
selectedThirdPartyProcessor: ""
}
});
</script>

View file

@ -0,0 +1,36 @@
@using Microsoft.AspNetCore.Mvc.Rendering
@model UpdateCoinSwitchSettingsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePageAndTitle(StoreNavPages.Index, "Update Store CoinSwitch Settings");
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage"/>
<div class="row">
<div class="col-md-10">
<form method="post">
<p>
You can obtain a merchant id at
<a href="https://coinswitch.co/?ref=G0UVY10JIE" target="_blank">
https://coinswitch.co
</a>
</p>
<div class="form-group">
<label asp-for="MerchantId"></label>
<input asp-for="MerchantId" class="form-control"/>
<span asp-validation-for="MerchantId" 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>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}

View file

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CoinSwitch</title>
<!--<script>-->
<!--//Initialize the CS object with your Merchant Id-->
<!--var payment = new Coinswitch('yadoshi');-->
<!--//When the library is available on the page, execute your code-->
<!--payment.on('Exchange:Ready', function() {-->
<!--// Add the "To Currency" and "To Currency Address"-->
<!--var config = {-->
<!--to_currency: 'btc',-->
<!--to_currency_address:'1CQ7ZdKeGWyCHF819MPB5vN4qYLjtKBpGE',-->
<!--to_amount: 1-->
<!--};-->
<!--// Open the payment wizard.-->
<!--payment.open(config);-->
<!--// Event: When the conversion request is created by the user.-->
<!--payment.on("Exchange:Initiated", function(orderId) {-->
<!--console.log("Exchange:Initiated CallbackFn", orderId);-->
<!--});-->
<!--// Event: When the conversion request is completed.-->
<!--payment.on("Exchange:Complete", function() {-->
<!--console.log("Exchange:Complete CallbackFn");-->
<!--})-->
<!--});-->
<!--</script>-->
<script>
window.onload = function () {
var qs = (function (a) {
if (a == "") return {};
var b = {};
for (var i = 0; i < a.length; ++i) {
var p = a[i].split('=', 2);
if (p.length == 1)
b[p[0]] = "";
else
b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
}
return b;
})(window.location.search.substr(1).split('&'));
var merchantId = qs["merchant_id"];
var toCurrencyDue = qs["toCurrencyDue"];
var toCurrencyAddress = qs["toCurrencyAddress"];
var toCurrency = qs["toCurrency"];
var url = "https://coinswitch.co/widget?widget_id=c0f2b60c&to=" + toCurrency + "&amount=" + toCurrencyDue + "&to_address=" + toCurrencyAddress;
document.getElementById("widget").src = url;
}
</script>
</head>
<body>
<iframe id="widget" style="left:0; top:0; width: 100%; height:100%; position: absolute;"
frameborder="0" scrolling="auto" style="overflow-y: hidden;border: 1px solid #ff7f50;border-top: none;">Can't
load widget
</iframe>
</body>
</html>

View file

@ -0,0 +1,38 @@
var CoinSwitchComponent =
{
props: ["toCurrency", "toCurrencyDue", "toCurrencyAddress", "merchantId", "autoload"],
data: function () {
},
computed: {
url: function () {
return window.location.origin + "/checkout/coinswitch.html?" +
"&toCurrency=" +
this.toCurrency +
"&toCurrencyAddress=" +
this.toCurrencyAddress +
"&toCurrencyDue=" +
this.toCurrencyDue +
(this.merchantId ? "&merchant_id=" + this.merchantId : "");
}
},
methods: {
openDialog: function (e) {
if (e && e.preventDefault) {
e.preventDefault();
}
var coinSwitchWindow = window.open(
this.url,
'CoinSwitch',
'width=600,height=470,toolbar=0,menubar=0,location=0,status=1,scrollbars=1,resizable=0,left=0,top=0');
coinSwitchWindow.opener = null;
coinSwitchWindow.focus();
}
},
mounted: function () {
if(this.autoload){
this.openDialog();
}
},
};