mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-12 19:02:01 +01:00
Topup
This commit is contained in:
parent
d9b6e465c0
commit
692a13e0c8
10 changed files with 468 additions and 5 deletions
|
@ -31,6 +31,9 @@
|
||||||
<Content Update="Plugins\BoltcardFactory\Views\ViewBoltcardFactory.cshtml">
|
<Content Update="Plugins\BoltcardFactory\Views\ViewBoltcardFactory.cshtml">
|
||||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Update="Plugins\BoltcardTopUp\Views\ScanCard.cshtml">
|
||||||
|
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||||
|
</Content>
|
||||||
<Content Update="Views\UIStorePullPayments\NewPullPayment.cshtml">
|
<Content Update="Views\UIStorePullPayments\NewPullPayment.cshtml">
|
||||||
<Pack>false</Pack>
|
<Pack>false</Pack>
|
||||||
</Content>
|
</Content>
|
||||||
|
|
|
@ -44,8 +44,6 @@ namespace BTCPayServer.Plugins.BoltcardBalance.Controllers
|
||||||
|
|
||||||
//return View($"{BoltcardBalancePlugin.ViewsDirectory}/BalanceView.cshtml", new BalanceViewModel()
|
//return View($"{BoltcardBalancePlugin.ViewsDirectory}/BalanceView.cshtml", new BalanceViewModel()
|
||||||
//{
|
//{
|
||||||
// Amount = 10000m,
|
|
||||||
// AmountCollected = 500m,
|
|
||||||
// AmountDue = 10000m,
|
// AmountDue = 10000m,
|
||||||
// Currency = "SATS",
|
// Currency = "SATS",
|
||||||
// Transactions = [new() { Date = DateTimeOffset.UtcNow, Balance = -3.0m }, new() { Date = DateTimeOffset.UtcNow, Balance = -5.0m }]
|
// Transactions = [new() { Date = DateTimeOffset.UtcNow, Balance = -3.0m }, new() { Date = DateTimeOffset.UtcNow, Balance = -5.0m }]
|
||||||
|
@ -59,8 +57,13 @@ namespace BTCPayServer.Plugins.BoltcardBalance.Controllers
|
||||||
var registration = await _dbContextFactory.GetBoltcardRegistration(issuerKey, boltData, true);
|
var registration = await _dbContextFactory.GetBoltcardRegistration(issuerKey, boltData, true);
|
||||||
if (registration is null)
|
if (registration is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
return await GetBalanceView(registration.PullPaymentId);
|
||||||
|
}
|
||||||
|
[NonAction]
|
||||||
|
public async Task<IActionResult> GetBalanceView(string ppId)
|
||||||
|
{
|
||||||
using var ctx = _dbContextFactory.CreateContext();
|
using var ctx = _dbContextFactory.CreateContext();
|
||||||
var pp = await ctx.PullPayments.FindAsync(registration.PullPaymentId);
|
var pp = await ctx.PullPayments.FindAsync(ppId);
|
||||||
if (pp is null)
|
if (pp is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var blob = pp.GetBlob();
|
var blob = pp.GetBlob();
|
||||||
|
@ -92,6 +95,12 @@ namespace BTCPayServer.Plugins.BoltcardBalance.Controllers
|
||||||
Status = payout.Entity.State
|
Status = payout.Entity.State
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
vm.Transactions.Add(new BalanceViewModel.Transaction()
|
||||||
|
{
|
||||||
|
Date = pp.StartDate,
|
||||||
|
Balance = blob.Limit,
|
||||||
|
Status = PayoutState.Completed
|
||||||
|
});
|
||||||
|
|
||||||
return View($"{BoltcardBalancePlugin.ViewsDirectory}/BalanceView.cshtml", vm);
|
return View($"{BoltcardBalancePlugin.ViewsDirectory}/BalanceView.cshtml", vm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,12 @@
|
||||||
<div class="col col-12 col-lg-12 mb-4">
|
<div class="col col-12 col-lg-12 mb-4">
|
||||||
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded">
|
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded">
|
||||||
<nav id="wizard-navbar">
|
<nav id="wizard-navbar">
|
||||||
|
@if (this.ViewData["NoCancelWizard"] is not true)
|
||||||
|
{
|
||||||
<a href="#" id="CancelWizard" class="cancel">
|
<a href="#" id="CancelWizard" class="cancel">
|
||||||
<vc:icon symbol="close" />
|
<vc:icon symbol="close" />
|
||||||
</a>
|
</a>
|
||||||
|
}
|
||||||
</nav>
|
</nav>
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
|
|
|
@ -69,6 +69,9 @@
|
||||||
});
|
});
|
||||||
setState("ShowBalance");
|
setState("ShowBalance");
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
setState("WaitingForCard");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
xhttp.open('GET', url, true);
|
xhttp.open('GET', url, true);
|
||||||
xhttp.send(new FormData());
|
xhttp.send(new FormData());
|
||||||
|
|
22
BTCPayServer/Plugins/BoltcardTopUp/BoltcardTopUpPlugin.cs
Normal file
22
BTCPayServer/Plugins/BoltcardTopUp/BoltcardTopUpPlugin.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
|
using BTCPayServer.Abstractions.Models;
|
||||||
|
using BTCPayServer.Abstractions.Services;
|
||||||
|
using BTCPayServer.Services.Apps;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using static BTCPayServer.Plugins.BoltcardFactory.BoltcardFactoryPlugin;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.BoltcardTopUp;
|
||||||
|
|
||||||
|
public class BoltcardTopUpPlugin : BaseBTCPayServerPlugin
|
||||||
|
{
|
||||||
|
public const string ViewsDirectory = "/Plugins/BoltcardTopUp/Views";
|
||||||
|
public override string Identifier => "BTCPayServer.Plugins.BoltcardTopUp";
|
||||||
|
public override string Name => "BoltcardTopUp";
|
||||||
|
public override string Description => "Add the ability to Top-Up a Boltcard";
|
||||||
|
|
||||||
|
public override void Execute(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<IUIExtension>(new UIExtension($"{ViewsDirectory}/NavExtension.cshtml", "header-nav"));
|
||||||
|
base.Execute(services);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,207 @@
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Filters;
|
||||||
|
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||||
|
using BTCPayServer.Services.Apps;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using BTCPayServer.Abstractions.Constants;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Services.Rates;
|
||||||
|
using BTCPayServer.ModelBinders;
|
||||||
|
using BTCPayServer.Plugins.BoltcardBalance;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.NTag424;
|
||||||
|
using BTCPayServer.Services;
|
||||||
|
using NBitcoin.DataEncoders;
|
||||||
|
using NBitcoin;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Org.BouncyCastle.Ocsp;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Plugins.BoltcardBalance.Controllers;
|
||||||
|
using BTCPayServer.HostedServices;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.BoltcardTopUp.Controllers
|
||||||
|
{
|
||||||
|
public class UIBoltcardTopUpController : Controller
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||||
|
private readonly SettingsRepository _settingsRepository;
|
||||||
|
private readonly BTCPayServerEnvironment _env;
|
||||||
|
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||||
|
private readonly RateFetcher _rateFetcher;
|
||||||
|
private readonly BTCPayNetworkProvider _networkProvider;
|
||||||
|
private readonly UIBoltcardBalanceController _boltcardBalanceController;
|
||||||
|
private readonly PullPaymentHostedService _ppService;
|
||||||
|
|
||||||
|
public UIBoltcardTopUpController(
|
||||||
|
ApplicationDbContextFactory dbContextFactory,
|
||||||
|
SettingsRepository settingsRepository,
|
||||||
|
BTCPayServerEnvironment env,
|
||||||
|
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
||||||
|
RateFetcher rateFetcher,
|
||||||
|
BTCPayNetworkProvider networkProvider,
|
||||||
|
UIBoltcardBalanceController boltcardBalanceController,
|
||||||
|
PullPaymentHostedService ppService,
|
||||||
|
CurrencyNameTable currencies)
|
||||||
|
{
|
||||||
|
_dbContextFactory = dbContextFactory;
|
||||||
|
_settingsRepository = settingsRepository;
|
||||||
|
_env = env;
|
||||||
|
_jsonSerializerSettings = jsonSerializerSettings;
|
||||||
|
_rateFetcher = rateFetcher;
|
||||||
|
_networkProvider = networkProvider;
|
||||||
|
_boltcardBalanceController = boltcardBalanceController;
|
||||||
|
_ppService = ppService;
|
||||||
|
Currencies = currencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CurrencyNameTable Currencies { get; }
|
||||||
|
|
||||||
|
[HttpGet("~/stores/{storeId}/boltcards/top-up")]
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
|
||||||
|
[AutoValidateAntiforgeryToken]
|
||||||
|
public async Task<IActionResult> Keypad(string storeId, string currency = null)
|
||||||
|
{
|
||||||
|
var settings = new PointOfSaleSettings
|
||||||
|
{
|
||||||
|
Title = "Boltcards Top-Up"
|
||||||
|
};
|
||||||
|
currency ??= this.HttpContext.GetStoreData().GetStoreBlob().DefaultCurrency;
|
||||||
|
var numberFormatInfo = Currencies.GetNumberFormatInfo(currency);
|
||||||
|
double step = Math.Pow(10, -numberFormatInfo.CurrencyDecimalDigits);
|
||||||
|
//var store = new Data.StoreData();
|
||||||
|
//var storeBlob = new StoreBlob();
|
||||||
|
|
||||||
|
return View($"{BoltcardTopUpPlugin.ViewsDirectory}/Keypad.cshtml", new ViewPointOfSaleViewModel
|
||||||
|
{
|
||||||
|
Title = settings.Title,
|
||||||
|
//StoreName = store.StoreName,
|
||||||
|
//BrandColor = storeBlob.BrandColor,
|
||||||
|
//CssFileId = storeBlob.CssFileId,
|
||||||
|
//LogoFileId = storeBlob.LogoFileId,
|
||||||
|
Step = step.ToString(CultureInfo.InvariantCulture),
|
||||||
|
//ViewType = BTCPayServer.Plugins.PointOfSale.PosViewType.Light,
|
||||||
|
//ShowCustomAmount = settings.ShowCustomAmount,
|
||||||
|
//ShowDiscount = settings.ShowDiscount,
|
||||||
|
//ShowSearch = settings.ShowSearch,
|
||||||
|
//ShowCategories = settings.ShowCategories,
|
||||||
|
//EnableTips = settings.EnableTips,
|
||||||
|
//CurrencyCode = settings.Currency,
|
||||||
|
//CurrencySymbol = numberFormatInfo.CurrencySymbol,
|
||||||
|
CurrencyCode = currency,
|
||||||
|
CurrencyInfo = new ViewPointOfSaleViewModel.CurrencyInfoData
|
||||||
|
{
|
||||||
|
CurrencySymbol = string.IsNullOrEmpty(numberFormatInfo.CurrencySymbol) ? settings.Currency : numberFormatInfo.CurrencySymbol,
|
||||||
|
Divisibility = numberFormatInfo.CurrencyDecimalDigits,
|
||||||
|
DecimalSeparator = numberFormatInfo.CurrencyDecimalSeparator,
|
||||||
|
ThousandSeparator = numberFormatInfo.NumberGroupSeparator,
|
||||||
|
Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern),
|
||||||
|
SymbolSpace = new[] { 2, 3 }.Contains(numberFormatInfo.CurrencyPositivePattern)
|
||||||
|
},
|
||||||
|
//Items = AppService.Parse(settings.Template, false),
|
||||||
|
//ButtonText = settings.ButtonText,
|
||||||
|
//CustomButtonText = settings.CustomButtonText,
|
||||||
|
//CustomTipText = settings.CustomTipText,
|
||||||
|
//CustomTipPercentages = settings.CustomTipPercentages,
|
||||||
|
//CustomCSSLink = settings.CustomCSSLink,
|
||||||
|
//CustomLogoLink = storeBlob.CustomLogo,
|
||||||
|
//AppId = "vouchers",
|
||||||
|
StoreId = storeId,
|
||||||
|
//Description = settings.Description,
|
||||||
|
//EmbeddedCSS = settings.EmbeddedCSS,
|
||||||
|
//RequiresRefundEmail = settings.RequiresRefundEmail
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("~/stores/{storeId}/boltcards/top-up")]
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
|
||||||
|
[AutoValidateAntiforgeryToken]
|
||||||
|
public IActionResult Keypad(string storeId,
|
||||||
|
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal amount, string currency)
|
||||||
|
{
|
||||||
|
return RedirectToAction(nameof(ScanCard),
|
||||||
|
new
|
||||||
|
{
|
||||||
|
storeId = storeId,
|
||||||
|
amount = amount,
|
||||||
|
currency = currency
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("~/stores/{storeId}/boltcards/top-up/scan")]
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
|
||||||
|
[AutoValidateAntiforgeryToken]
|
||||||
|
public async Task<IActionResult> ScanCard(string storeId,
|
||||||
|
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal amount, string currency)
|
||||||
|
{
|
||||||
|
return View($"{BoltcardTopUpPlugin.ViewsDirectory}/ScanCard.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("~/stores/{storeId}/boltcards/top-up/scan")]
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
|
||||||
|
public async Task<IActionResult> ScanCard(string storeId,
|
||||||
|
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal amount, string currency, string p, string c)
|
||||||
|
{
|
||||||
|
//return View($"{BoltcardBalancePlugin.ViewsDirectory}/BalanceView.cshtml", new BoltcardBalance.ViewModels.BalanceViewModel()
|
||||||
|
//{
|
||||||
|
// AmountDue = 10000m,
|
||||||
|
// Currency = "SATS",
|
||||||
|
// Transactions = [new() { Date = DateTimeOffset.UtcNow, Balance = -3.0m }, new() { Date = DateTimeOffset.UtcNow, Balance = -5.0m }]
|
||||||
|
//});
|
||||||
|
|
||||||
|
var issuerKey = await _settingsRepository.GetIssuerKey(_env);
|
||||||
|
var boltData = issuerKey.TryDecrypt(p);
|
||||||
|
if (boltData?.Uid is null)
|
||||||
|
return NotFound();
|
||||||
|
var id = issuerKey.GetId(boltData.Uid);
|
||||||
|
var registration = await _dbContextFactory.GetBoltcardRegistration(issuerKey, boltData, true);
|
||||||
|
if (registration is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var pp = await _ppService.GetPullPayment(registration.PullPaymentId, false);
|
||||||
|
|
||||||
|
var rules = this.HttpContext.GetStoreData().GetStoreBlob().GetRateRules(_networkProvider);
|
||||||
|
var rateResult = await _rateFetcher.FetchRate(new Rating.CurrencyPair("BTC", currency), rules, default);
|
||||||
|
var cryptoAmount = Math.Round(amount / rateResult.BidAsk.Bid, 11);
|
||||||
|
|
||||||
|
var ppCurrency = pp.GetBlob().Currency;
|
||||||
|
rateResult = await _rateFetcher.FetchRate(new Rating.CurrencyPair(ppCurrency, currency), rules, default);
|
||||||
|
var ppAmount = Math.Round(amount / rateResult.BidAsk.Bid, Currencies.GetNumberFormatInfo(ppCurrency).CurrencyDecimalDigits);
|
||||||
|
|
||||||
|
using var ctx = _dbContextFactory.CreateContext();
|
||||||
|
var payout = new Data.PayoutData()
|
||||||
|
{
|
||||||
|
Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20)),
|
||||||
|
Date = DateTimeOffset.UtcNow,
|
||||||
|
State = PayoutState.AwaitingApproval,
|
||||||
|
PullPaymentDataId = registration.PullPaymentId,
|
||||||
|
PaymentMethodId = new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToString(),
|
||||||
|
Destination = null,
|
||||||
|
StoreDataId = storeId
|
||||||
|
};
|
||||||
|
var payoutBlob = new PayoutBlob()
|
||||||
|
{
|
||||||
|
CryptoAmount = -cryptoAmount,
|
||||||
|
Amount = -ppAmount,
|
||||||
|
Destination = null,
|
||||||
|
Metadata = new JObject(),
|
||||||
|
};
|
||||||
|
payout.SetBlob(payoutBlob, _jsonSerializerSettings);
|
||||||
|
await ctx.Payouts.AddAsync(payout);
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
_boltcardBalanceController.ViewData["NoCancelWizard"] = true;
|
||||||
|
return await _boltcardBalanceController.GetBalanceView(registration.PullPaymentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
BTCPayServer/Plugins/BoltcardTopUp/Views/Keypad.cshtml
Normal file
48
BTCPayServer/Plugins/BoltcardTopUp/Views/Keypad.cshtml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
|
||||||
|
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
|
||||||
|
@{
|
||||||
|
Layout = "PointOfSale/Public/_Layout";
|
||||||
|
Csp.UnsafeEval();
|
||||||
|
}
|
||||||
|
@section PageHeadContent {
|
||||||
|
<link href="~/pos/keypad.css" asp-append-version="true" rel="stylesheet" />
|
||||||
|
}
|
||||||
|
@section PageFootContent {
|
||||||
|
<script>var srvModel = @Safe.Json(Model);</script>
|
||||||
|
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
|
||||||
|
<script src="~/pos/common.js" asp-append-version="true"></script>
|
||||||
|
<script src="~/pos/keypad.js" asp-append-version="true"></script>
|
||||||
|
}
|
||||||
|
<div id="PosKeypad" class="public-page-wrap">
|
||||||
|
<partial name="_StatusMessage" />
|
||||||
|
<partial name="_StoreHeader" model="(Model.Title, null as StoreBrandingViewModel)" />
|
||||||
|
|
||||||
|
<form id="app" method="post"
|
||||||
|
asp-route-storeId="@Model.StoreId"
|
||||||
|
asp-antiforgery="true" v-on:submit="handleFormSubmit" class="d-flex flex-column gap-4 my-auto" v-cloak>
|
||||||
|
<input type="hidden" name="posdata" v-model="posdata" id="posdata">
|
||||||
|
<input type="hidden" name="amount" v-model="totalNumeric">
|
||||||
|
<input type="hidden" name="currency" v-model="currencyCode">
|
||||||
|
<div ref="display" class="d-flex flex-column align-items-center px-4 mb-auto">
|
||||||
|
<div class="fw-semibold text-muted" id="Currency">{{currencyCode}}</div>
|
||||||
|
<div class="fw-bold lh-sm" ref="amount" v-bind:style="{ fontSize: `${fontSize}px` }" id="Amount">{{ formatCurrency(total, false) }}</div>
|
||||||
|
<div class="text-muted text-center mt-2" id="Calculation">{{ calculation }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="keypad">
|
||||||
|
<button v-for="k in keys" :key="k" :disabled="k === '+' && mode !== 'amounts'" v-on:click.prevent="keyPressed(k)" v-on:dblclick.prevent="doubleClick(k)" type="button" class="btn btn-secondary btn-lg" :data-key="k">{{ k }}</button>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-lg btn-primary mx-3" type="submit" :disabled="payButtonLoading" id="pay-button">
|
||||||
|
<div v-if="payButtonLoading" class="spinner-border spinner-border-sm" id="pay-button-spinner" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<template v-else>Top-Up Card</template>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<footer class="store-footer">
|
||||||
|
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
|
||||||
|
Powered by <partial name="_StoreFooterLogo" />
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
18
BTCPayServer/Plugins/BoltcardTopUp/Views/NavExtension.cshtml
Normal file
18
BTCPayServer/Plugins/BoltcardTopUp/Views/NavExtension.cshtml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
@using BTCPayServer.Client
|
||||||
|
@using BTCPayServer.Plugins.BoltcardFactory
|
||||||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
@using BTCPayServer.Views.Apps
|
||||||
|
@using BTCPayServer.Abstractions.Extensions
|
||||||
|
@using BTCPayServer.Abstractions.TagHelpers
|
||||||
|
@using BTCPayServer
|
||||||
|
|
||||||
|
@{
|
||||||
|
var storeId = Context.GetStoreData().Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a asp-area="" asp-controller="UIBoltcardTopUp" asp-action="Keypad" asp-route-storeId="@storeId" class="nav-link">
|
||||||
|
<vc:icon symbol="pay-button" />
|
||||||
|
<span>Boltcard Top-Up</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
150
BTCPayServer/Plugins/BoltcardTopUp/Views/ScanCard.cshtml
Normal file
150
BTCPayServer/Plugins/BoltcardTopUp/Views/ScanCard.cshtml
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Boltcard TopUps";
|
||||||
|
ViewData["ShowFooter"] = false;
|
||||||
|
Layout = "/Views/Shared/_LayoutWizard.cshtml";
|
||||||
|
}
|
||||||
|
|
||||||
|
@section PageHeadContent
|
||||||
|
{
|
||||||
|
<style>
|
||||||
|
.amount-col {
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
}
|
||||||
|
|
||||||
|
<header class="text-center">
|
||||||
|
<h1>Boltcard Top-Up</h1>
|
||||||
|
<p class="lead text-secondary mt-3" id="explanation">Scan your card to top it up</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="body" class="my-4">
|
||||||
|
<div id="actions" class="d-flex align-items-center justify-content-center d-none">
|
||||||
|
<div class="d-flex gap-3 mt-3 mt-sm-0">
|
||||||
|
<a id="start-scan-btn" class="btn btn-primary" href="#">Ask permission...</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="qr" class="d-flex flex-column align-items-center justify-content-center d-none">
|
||||||
|
<div class="d-inline-flex flex-column">
|
||||||
|
<div class="qr-container mb-2">
|
||||||
|
<vc:qr-code data="@Context.Request.GetCurrentUrl()" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-secondary">NFC not supported in this device</p>
|
||||||
|
</div>
|
||||||
|
<div id="scanning-btn" class="d-flex align-items-center justify-content-center d-none">
|
||||||
|
<div class="d-flex gap-3 mt-3 mt-sm-0">
|
||||||
|
<a id="scanning-btn-link" class="action-button" style="font-size: 50px;" ></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="balance" class="row">
|
||||||
|
<div id="balance-table"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var permissionGranted = false;
|
||||||
|
var ndef = null;
|
||||||
|
var abortController = null;
|
||||||
|
var scanned = false;
|
||||||
|
|
||||||
|
function delay(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
async function showBalance(lnurlw) {
|
||||||
|
setState("Submitting");
|
||||||
|
await delay(1000);
|
||||||
|
var url = window.location.href.replace("#", "");
|
||||||
|
url = url.split("?")[0] + "?" + lnurlw.split("?")[1] + "&" + url.split("?")[1];
|
||||||
|
// url = "https://testnet.demo.btcpayserver.org/boltcards/balance?p=...&c=..."
|
||||||
|
|
||||||
|
scanned = true;
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200 && this.responseText) {
|
||||||
|
document.getElementById("balance-table").innerHTML = this.responseText;
|
||||||
|
setState("ShowBalance");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
scanned = false;
|
||||||
|
setState("WaitingForCard");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open('POST', url, true);
|
||||||
|
xhttp.send(new FormData());
|
||||||
|
}
|
||||||
|
async function startScan() {
|
||||||
|
if (!('NDEFReader' in window)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ndef = new NDEFReader();
|
||||||
|
abortController = new AbortController();
|
||||||
|
abortController.signal.onabort = () => setState("WaitingForCard");
|
||||||
|
|
||||||
|
await ndef.scan({ signal: abortController.signal })
|
||||||
|
setState("WaitingForCard");
|
||||||
|
ndef.onreading = async ({ message }) => {
|
||||||
|
const record = message.records[0];
|
||||||
|
const textDecoder = new TextDecoder('utf-8');
|
||||||
|
const decoded = textDecoder.decode(record.data);
|
||||||
|
await showBalance(decoded);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setState(state)
|
||||||
|
{
|
||||||
|
document.getElementById("actions").classList.add("d-none");
|
||||||
|
document.getElementById("qr").classList.add("d-none");
|
||||||
|
document.getElementById("scanning-btn").classList.add("d-none");
|
||||||
|
document.getElementById("balance").classList.add("d-none");
|
||||||
|
|
||||||
|
if (state === "NFCNotSupported")
|
||||||
|
{
|
||||||
|
document.getElementById("qr").classList.remove("d-none");
|
||||||
|
}
|
||||||
|
else if (state === "WaitingForPermission")
|
||||||
|
{
|
||||||
|
|
||||||
|
document.getElementById("actions").classList.remove("d-none");
|
||||||
|
}
|
||||||
|
else if (state === "WaitingForCard")
|
||||||
|
{
|
||||||
|
document.getElementById("scanning-btn").classList.remove("d-none");
|
||||||
|
document.getElementById("scanning-btn-link").innerHTML = "<i class=\"fa fa-wifi\"></i>";
|
||||||
|
}
|
||||||
|
else if (state == "Submitting")
|
||||||
|
{
|
||||||
|
document.getElementById("scanning-btn").classList.remove("d-none");
|
||||||
|
document.getElementById("scanning-btn-link").innerHTML = "<i class=\"fa fa-spinner\"></i>"
|
||||||
|
}
|
||||||
|
else if (state == "ShowBalance") {
|
||||||
|
document.getElementById("explanation").classList.add("d-none");
|
||||||
|
document.getElementById("scanning-btn").classList.remove("d-none");
|
||||||
|
document.getElementById("scanning-btn-link").innerHTML = "<i class=\"fa fa-bitcoin\"></i>";
|
||||||
|
document.getElementById("balance").classList.remove("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
|
var nfcSupported = 'NDEFReader' in window;
|
||||||
|
if (!nfcSupported) {
|
||||||
|
setState("NFCNotSupported");
|
||||||
|
//setState("ShowBalance");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setState("WaitingForPermission");
|
||||||
|
var granted = (await navigator.permissions.query({ name: 'nfc' })).state === 'granted';
|
||||||
|
if (granted)
|
||||||
|
{
|
||||||
|
setState("WaitingForCard");
|
||||||
|
startScan();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate('click', "#start-scan-btn", startScan);
|
||||||
|
//showBalance("lnurl://ewfw?p=test&c=test");
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
@using BTCPayServer.Abstractions.Contracts
|
@using BTCPayServer.Abstractions.Contracts
|
||||||
@model (string Title, StoreBrandingViewModel StoreBranding)
|
@model (string Title, StoreBrandingViewModel StoreBranding)
|
||||||
@{
|
@{
|
||||||
var logoUrl = !string.IsNullOrEmpty(Model.StoreBranding.LogoFileId)
|
var logoUrl = !string.IsNullOrEmpty(Model.StoreBranding?.LogoFileId)
|
||||||
? await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.StoreBranding.LogoFileId)
|
? await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.StoreBranding?.LogoFileId)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
<header class="store-header" v-pre>
|
<header class="store-header" v-pre>
|
||||||
|
|
Loading…
Add table
Reference in a new issue