mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
add voucher poc
This commit is contained in:
@@ -53,6 +53,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.FileSe
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.DynamicReports", "Plugins\BTCPayServer.Plugins.DynamicReports\BTCPayServer.Plugins.DynamicReports.csproj", "{BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.DynamicReports", "Plugins\BTCPayServer.Plugins.DynamicReports\BTCPayServer.Plugins.DynamicReports.csproj", "{BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Vouchers", "Plugins\BTCPayServer.Plugins.Vouchers\BTCPayServer.Plugins.Vouchers.csproj", "{ED061A37-488F-429C-A291-6A5188A47443}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -253,6 +255,14 @@ Global
|
|||||||
{BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
|
{BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
|
{BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
|
{BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{ED061A37-488F-429C-A291-6A5188A47443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{ED061A37-488F-429C-A291-6A5188A47443}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{ED061A37-488F-429C-A291-6A5188A47443}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{ED061A37-488F-429C-A291-6A5188A47443}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{ED061A37-488F-429C-A291-6A5188A47443}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{ED061A37-488F-429C-A291-6A5188A47443}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{ED061A37-488F-429C-A291-6A5188A47443}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{ED061A37-488F-429C-A291-6A5188A47443}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962}
|
{B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<!-- -->
|
||||||
|
<!-- Plugin specific properties -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<Product>Vouchers</Product>
|
||||||
|
<Description>Allows you to give users Bitcoin vouchers.</Description>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
<!-- Plugin development properties -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||||
|
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||||
|
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- This will make sure that referencing BTCPayServer doesn't put any artifact in the published directory -->
|
||||||
|
<ItemDefinitionGroup>
|
||||||
|
<ProjectReference>
|
||||||
|
<Properties>StaticWebAssetsEnabled=false</Properties>
|
||||||
|
<Private>false</Private>
|
||||||
|
<ExcludeAssets>runtime;native;build;buildTransitive;contentFiles</ExcludeAssets>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Resources\**" />
|
||||||
|
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Resources" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AdditionalFiles Include="Views\Voucher\Edit.cshtml" />
|
||||||
|
<AdditionalFiles Include="Views\Shared\Nip05Nav.cshtml" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
22
Plugins/BTCPayServer.Plugins.Vouchers/Program.cs
Normal file
22
Plugins/BTCPayServer.Plugins.Vouchers/Program.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
|
using BTCPayServer.Abstractions.Models;
|
||||||
|
using BTCPayServer.Abstractions.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.Vouchers
|
||||||
|
{
|
||||||
|
public class VoucherPlugin : BaseBTCPayServerPlugin
|
||||||
|
{
|
||||||
|
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
|
||||||
|
{
|
||||||
|
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.11.0"}
|
||||||
|
};
|
||||||
|
|
||||||
|
public override void Execute(IServiceCollection applicationBuilder)
|
||||||
|
{
|
||||||
|
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("VoucherNav",
|
||||||
|
"store-integrations-nav"));
|
||||||
|
base.Execute(applicationBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
@using BTCPayServer.Abstractions.Contracts
|
||||||
|
@using BTCPayServer.Abstractions.Extensions
|
||||||
|
@using BTCPayServer.Client
|
||||||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
@inject IScopeProvider ScopeProvider
|
||||||
|
@{
|
||||||
|
var storeId = ScopeProvider.GetCurrentStoreId();
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(storeId))
|
||||||
|
{
|
||||||
|
<li class="nav-item">
|
||||||
|
<a asp-controller="Voucher" asp-action="ListVouchers" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Voucher")"
|
||||||
|
permission="@Policies.CanModifyStoreSettings">
|
||||||
|
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style=" width: 20px;
|
||||||
|
margin-right: 6px;" viewBox="0 0 1536.000000 1536.000000" preserveAspectRatio="xMidYMid meet">
|
||||||
|
<g transform="translate(0.000000,1536.000000) scale(0.100000,-0.100000)" fill="currentColor" stroke="none">
|
||||||
|
<path d="M1434 12669 c-3 -6 -6 -49 -5 -95 2 -130 -17 -333 -40 -414 -75 -273 -301 -506 -574 -593 -85 -27 -163 -37 -349 -42 -128 -4 -182 -9 -188 -18 -4 -6 -8 -1315 -8 -2908 0 -2383 2 -2898 13 -2907 8 -7 102 -15 228 -21 233 -11 290 -22 409 -80 80 -39 93 -47 150 -90 154 -117 270 -284 320 -462 19 -67 33 -225 43 -454 l2 -60 5320 0 5320 0 6 195 c3 107 12 220 18 250 61 274 239 501 484 618 147 70 311 98 505 86 100 -6 115 -5 132 11 20 18 20 40 20 2915 0 2783 -1 2898 -18 2913 -15 14 -32 16 -98 11 -105 -8 -342 11 -429 34 -39 10 -100 34 -135 52 -36 18 -77 39 -91 47 -51 26 -187 152 -236 220 -74 99 -124 204 -158 333 -3 14 -10 120 -15 237 -5 117 -11 217 -15 223 -9 15 -10601 14 -10611 -1z m10099 -599 c40 -163 117 -339 211 -480 88 -131 295 -327 441 -417 101 -63 373 -173 425 -173 9 0 21 -5 28 -12 17 -17 17 -4759 0 -4776 -7 -7 -21 -12 -31 -12 -38 0 -230 -72 -342 -127 -194 -97 -406 -290 -541 -493 -73 -110 -172 -341 -189 -442 -4 -28 -12 -39 -28 -43 -12 -3 -2166 -4 -4787 -3 l-4764 3 -9 35 c-103 421 -388 777 -772 961 -103 49 -269 109 -303 109 -11 0 -25 6 -31 14 -8 10 -10 642 -9 2391 3 2102 5 2379 18 2385 8 5 44 16 80 26 327 89 615 296 814 586 74 109 196 383 196 442 0 8 4 26 10 40 l10 26 4782 -2 4782 -3 9 -35z"/>
|
||||||
|
<path d="M8080 11514 c-84 -7 -265 -36 -309 -50 -25 -8 -54 -14 -65 -14 -12 0 -41 -6 -66 -14 -25 -8 -72 -22 -105 -32 -252 -73 -428 -148 -680 -289 -112 -63 -269 -174 -390 -278 -535 -457 -865 -1031 -998 -1732 -64 -336 -64 -720 -1 -1060 43 -230 104 -439 179 -615 35 -84 128 -270 162 -328 60 -101 83 -137 147 -227 234 -333 555 -630 901 -833 49 -29 97 -57 105 -62 28 -17 202 -100 254 -121 154 -63 196 -79 246 -94 30 -10 64 -21 76 -25 11 -5 58 -17 105 -28 291 -71 401 -84 699 -85 205 -1 270 3 400 21 269 40 483 99 765 214 65 26 272 128 355 174 327 181 673 506 899 843 336 502 486 975 508 1606 11 325 -58 774 -164 1060 -9 22 -21 56 -28 75 -170 460 -476 899 -839 1203 -168 141 -275 217 -469 331 -211 124 -560 255 -827 310 -197 41 -276 49 -545 51 -148 2 -290 1 -315 -1z m520 -569 c389 -42 726 -162 1038 -369 303 -201 580 -491 738 -772 15 -27 38 -67 51 -89 12 -22 27 -50 33 -63 5 -13 21 -46 34 -75 83 -177 153 -419 192 -667 13 -85 15 -154 12 -360 -3 -140 -11 -282 -17 -315 -7 -33 -20 -96 -28 -140 -73 -378 -238 -701 -525 -1031 -229 -265 -429 -424 -703 -561 -165 -83 -193 -95 -323 -139 -516 -173 -1108 -169 -1587 11 -38 15 -78 29 -88 32 -9 3 -70 31 -135 63 -399 195 -732 493 -977 875 -59 91 -171 318 -218 440 -21 55 -71 225 -91 310 -65 281 -65 709 0 1010 26 117 81 293 120 380 92 206 146 306 239 445 265 392 664 708 1119 885 146 57 355 112 491 129 141 18 463 19 625 1z"/>
|
||||||
|
<path d="M7455 10436 c-12 -19 -16 -48 -14 -129 1 -58 -2 -110 -5 -116 -5 -7 -56 -11 -145 -11 -93 0 -141 -4 -149 -12 -9 -9 -12 -364 -12 -1534 0 -837 3 -1529 6 -1538 5 -14 27 -16 139 -16 73 0 140 -3 149 -6 13 -5 16 -26 16 -123 0 -78 4 -123 13 -134 11 -16 37 -17 277 -15 l265 3 3 124 c1 71 7 130 14 138 11 13 141 19 172 7 13 -5 16 -26 16 -128 0 -82 4 -126 12 -134 9 -9 84 -12 275 -12 249 0 263 1 273 19 5 11 10 72 10 135 0 133 -16 117 130 136 310 43 561 216 714 494 32 57 50 105 77 201 25 90 36 317 20 433 -20 154 -100 308 -220 427 -34 33 -61 65 -61 73 0 7 23 51 50 98 28 47 50 87 50 90 0 3 6 20 13 37 39 91 67 232 67 337 0 62 -23 207 -41 260 -68 199 -196 367 -364 475 -107 69 -271 135 -368 149 -64 9 -67 15 -67 140 0 168 22 156 -283 156 -141 0 -259 -4 -267 -10 -12 -7 -16 -38 -20 -137 l-5 -128 -92 -3 c-110 -3 -102 -15 -106 158 l-2 115 -262 3 -262 2 -16 -24z m1401 -877 c75 -28 154 -102 184 -172 14 -31 25 -81 28 -122 8 -123 -36 -222 -127 -289 -89 -65 -97 -66 -677 -66 -335 0 -525 4 -529 10 -9 14 -7 642 2 651 14 13 69 14 569 12 478 -2 492 -3 550 -24z m29 -1194 c95 -11 210 -98 260 -195 13 -25 29 -74 35 -108 31 -157 -65 -339 -218 -413 -36 -18 -72 -25 -153 -29 -154 -10 -1060 -3 -1071 8 -6 6 -12 526 -8 720 0 7 9 16 19 20 24 8 1065 6 1136 -3z"/>
|
||||||
|
<path d="M3455 11427 c-3 -7 -4 -94 -3 -192 l3 -180 275 0 275 0 0 190 0 190 -273 3 c-216 2 -274 0 -277 -11z"/>
|
||||||
|
<path d="M3455 10357 c-3 -7 -4 -94 -3 -192 l3 -180 275 0 275 0 0 190 0 190 -273 3 c-216 2 -274 0 -277 -11z"/>
|
||||||
|
<path d="M3462 9322 c-9 -7 -12 -52 -10 -193 l3 -184 275 0 275 0 3 184 c2 141 -1 186 -10 193 -7 4 -128 8 -268 8 -140 0 -261 -4 -268 -8z"/>
|
||||||
|
<path d="M3455 8247 c-3 -7 -4 -94 -3 -192 l3 -180 275 0 275 0 0 190 0 190 -273 3 c-216 2 -274 0 -277 -11z"/>
|
||||||
|
<path d="M3455 7207 c-3 -7 -4 -94 -3 -192 l3 -180 275 0 275 0 0 190 0 190 -273 3 c-216 2 -274 0 -277 -11z"/>
|
||||||
|
<path d="M3463 6173 c-10 -3 -13 -52 -13 -193 0 -186 0 -188 23 -194 37 -10 484 -7 511 4 l26 10 0 184 c0 158 -2 185 -16 190 -19 7 -513 7 -531 -1z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<span>Vouchers</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
@using BTCPayServer.Abstractions.Extensions
|
||||||
|
@using BTCPayServer.TagHelpers
|
||||||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
@using BTCPayServer
|
||||||
|
@using BTCPayServer.Data
|
||||||
|
@model List<BTCPayServer.Plugins.Vouchers.VoucherController.VoucherViewModel>
|
||||||
|
@{
|
||||||
|
ViewData.SetActivePage("Voucher", "Create Voucher", "Voucher");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
<form method="post" asp-action="CreateVoucher">
|
||||||
|
|
||||||
|
<div class="sticky-header-setup"></div>
|
||||||
|
<div class="sticky-header d-sm-flex align-items-center justify-content-between">
|
||||||
|
<h2 class="mb-0">@ViewData["Title"]</h2>
|
||||||
|
<div class="d-flex gap-3 mt-3 mt-sm-0">
|
||||||
|
<button name="command" type="submit" value="save" class="btn btn-primary">Create</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<partial name="_StatusMessage"/>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-8 col-xxl-constrain">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="form-group flex-fill me-4">
|
||||||
|
<label for="amount" class="form-label">Amount</label>
|
||||||
|
<input inputmode="decimal" min="0" name="amount" id="amount" class="form-control"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="currency" class="form-label">Currency</label>
|
||||||
|
<input name="currency" id="currency" class="form-control w-auto" value="@Context.GetStoreData().GetStoreBlob().DefaultCurrency" currency-selection/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
@using BTCPayServer.Abstractions.Extensions
|
||||||
|
@using BTCPayServer.Abstractions.Contracts
|
||||||
|
@using BTCPayServer.Abstractions.Models
|
||||||
|
@using BTCPayServer.Client
|
||||||
|
@using BTCPayServer.Services
|
||||||
|
@inject IScopeProvider ScopeProvider
|
||||||
|
@inject DisplayFormatter DisplayFormatter
|
||||||
|
@model List<BTCPayServer.Plugins.Vouchers.VoucherController.VoucherViewModel>
|
||||||
|
@{
|
||||||
|
ViewData.SetActivePage("Voucher", "Voucher", "Voucher");
|
||||||
|
|
||||||
|
var storeId = ScopeProvider.GetCurrentStoreId();
|
||||||
|
}
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
|
||||||
|
<div class="sticky-header-setup"></div>
|
||||||
|
<div class="sticky-header d-sm-flex align-items-center justify-content-between">
|
||||||
|
<h2 class="mb-0">@ViewData["Title"]</h2>
|
||||||
|
<div class="d-flex gap-3 mt-3 mt-sm-0">
|
||||||
|
<a asp-controller="Voucher" asp-action="CreateVoucher" asp-route-storeId="@storeId" class="btn btn-primary">Create</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<partial name="_StatusMessage"/>
|
||||||
|
|
||||||
|
|
||||||
|
@if (Model.Any())
|
||||||
|
{
|
||||||
|
@foreach (var pp in Model)
|
||||||
|
{
|
||||||
|
<script id="tooptip_template_@pp.Id" type="text/template">
|
||||||
|
<span>Awaiting: <span class="float-end">@pp.Progress.AwaitingFormatted</span></span>
|
||||||
|
<br />
|
||||||
|
<span>Completed: <span class="float-end">@pp.Progress.CompletedFormatted</span></span>
|
||||||
|
<br />
|
||||||
|
<span>Limit: <span class="float-end">@pp.Progress.LimitFormatted</span></span>
|
||||||
|
@if (pp.Progress.ResetIn != null)
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
<span>Resets in: <span class="float-end">@pp.Progress.ResetIn</span></span>
|
||||||
|
}
|
||||||
|
@if (pp.Progress.EndIn != null)
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
<span>Expires in: <span class="float-end">@pp.Progress.EndIn</span></span>
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Amount</th>
|
||||||
|
<th scope="col">Progress</th>
|
||||||
|
<th scope="col" class="text-end">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var pp in Model)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a asp-action="EditPullPayment"
|
||||||
|
asp-controller="UIPullPayment"
|
||||||
|
asp-route-storeId="@storeId"
|
||||||
|
asp-route-pullPaymentId="@pp.Id">
|
||||||
|
@pp.Name
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>@DisplayFormatter.Currency(pp.Amount, pp.Currency)</td>
|
||||||
|
<td>@string.Join(", ", pp.PaymentMethods.Select(id => id.ToPrettyString()))</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<div class="progress ppProgress" data-pp="@pp.Id" data-bs-toggle="tooltip" data-bs-html="true">
|
||||||
|
<div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.CompletedPercent"
|
||||||
|
aria-valuemin="0" aria-valuemax="100" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width:@(pp.Progress.CompletedPercent)%;">
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.AwaitingPercent"
|
||||||
|
aria-valuemin="0" aria-valuemax="100" style="background-color:orange; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width:@(pp.Progress.AwaitingPercent)%;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<a asp-action="ArchivePullPayment"
|
||||||
|
asp-controller="UIStorePullPayments"
|
||||||
|
permission="@Policies.CanArchivePullPayments"
|
||||||
|
asp-route-storeId="@storeId"
|
||||||
|
asp-route-pullPaymentId="@pp.Id"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#ConfirmModal"
|
||||||
|
data-description="Do you really want to archive the pull payment <strong>@Html.Encode(pp.Name)</strong>?">
|
||||||
|
Archive
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
<span> - </span>
|
||||||
|
<a asp-action="ViewPullPayment"
|
||||||
|
asp-controller="UIPullPayment"
|
||||||
|
asp-route-pullPaymentId="@pp.Id">
|
||||||
|
Full view
|
||||||
|
</a>
|
||||||
|
<span> - </span>
|
||||||
|
<a asp-action="View"
|
||||||
|
asp-controller="Voucher"
|
||||||
|
asp-route-id="@pp.Id">
|
||||||
|
Print view
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="_Confirm" model="@(new ConfirmModel("Archive pull payment", "Do you really want to archive the pull payment?", "Archive"))"/>
|
||||||
|
|
||||||
|
@section PageFootContent {
|
||||||
|
<script>
|
||||||
|
const ppProgresses = document.getElementsByClassName("ppProgress");
|
||||||
|
for (var i = 0; i < ppProgresses.length; i++) {
|
||||||
|
var pp = ppProgresses[i];
|
||||||
|
var ppId = pp.getAttribute("data-pp");
|
||||||
|
var template = document.getElementById("tooptip_template_" + ppId);
|
||||||
|
pp.setAttribute("title", template.innerHTML);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p class="text-secondary mt-4">
|
||||||
|
There are no active vouchers.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
@model BTCPayServer.Plugins.Vouchers.VoucherController.VoucherViewModel
|
||||||
|
@using BTCPayServer.Components.QRCode
|
||||||
|
@using BTCPayServer.Services
|
||||||
|
@inject BTCPayServerEnvironment Env
|
||||||
|
@inject DisplayFormatter DisplayFormatter
|
||||||
|
@{
|
||||||
|
Layout = null;
|
||||||
|
ViewData["Title"] = Model.Name;
|
||||||
|
|
||||||
|
string lnurl = null;
|
||||||
|
if (Model.SupportsLNURL)
|
||||||
|
{
|
||||||
|
lnurl = LNURL.LNURL.EncodeBech32(new Uri(Url.Action("GetLNURLForPullPayment", "UILNURL", new {cryptoCode = "BTC", pullPaymentId = Model.Id}, Context.Request.Scheme, Context.Request.Host.ToString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullView = Url.Action("ViewPullPayment", "UIPullPayment", new {pullPaymentId = Model.Id}, Context.Request.Scheme, Context.Request.Host.ToString());
|
||||||
|
}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" @(Env.IsDeveloping ? " data-devenv" : "")>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<partial name="LayoutHead"/>
|
||||||
|
<partial name="LayoutHeadStoreBranding" model="@(Model.BrandColor, Model.CssFileId, "", "")"/>
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<style>
|
||||||
|
#InvoiceSummary { gap: var(--btcpay-space-l); }
|
||||||
|
#PaymentDetails table tbody tr:first-child td { padding-top: 1rem; }
|
||||||
|
#PaymentDetails table tbody:not(:last-child) tr:last-child > th,td { padding-bottom: 1rem; }
|
||||||
|
#posData td > table:last-child { margin-bottom: 0 !important; }
|
||||||
|
#posData table > tbody > tr:first-child > td > h4 { margin-top: 0 !important; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="min-vh-100">
|
||||||
|
<div class="public-page-wrap">
|
||||||
|
<main class="flex-grow-1">
|
||||||
|
<div class="container" style="max-width:720px;">
|
||||||
|
<partial name="_StatusMessage" model="@(new ViewDataDictionary(ViewData) {{"Margin", "mb-4"}})"/>
|
||||||
|
|
||||||
|
<div class="d-flex flex-column justify-content-center gap-4">
|
||||||
|
<partial name="_StoreHeader" model="(Model.StoreName, Model.LogoFileId)"/>
|
||||||
|
<h2 class="w-100 text-center">Voucher</h2>
|
||||||
|
<div id="InvoiceSummary" class="bg-tile p-3 p-sm-4 rounded d-flex flex-wrap align-items-center justify-content-center">
|
||||||
|
|
||||||
|
@if (lnurl != null)
|
||||||
|
{
|
||||||
|
<vc:qr-code data="@lnurl"></vc:qr-code>
|
||||||
|
<p>Scan with a Bitcoin Lightning wallet to redeem your voucher </p>
|
||||||
|
}
|
||||||
|
<a href="@fullView">
|
||||||
|
<vc:qr-code data="@fullView"></vc:qr-code>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p>Scan or open this link in your browser for the full option list for redemption</p>
|
||||||
|
|
||||||
|
<dl class="d-flex flex-column gap-4 mb-0 flex-fill">
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
|
|
||||||
|
<dd class="text-muted mb-0 fw-semibold">Amount</dd>
|
||||||
|
</div>
|
||||||
|
<dt class="fs-2 mb-0 text-nowrap fw-semibold">@DisplayFormatter.Currency(Model.Amount, Model.Currency, DisplayFormatter.CurrencyFormat.Symbol)</dt>
|
||||||
|
</div>
|
||||||
|
@if (!string.IsNullOrEmpty(Model.Description))
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
@Model.Description
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<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>
|
||||||
|
<partial name="LayoutFoot"/>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.print();
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
218
Plugins/BTCPayServer.Plugins.Vouchers/VoucherController.cs
Normal file
218
Plugins/BTCPayServer.Plugins.Vouchers/VoucherController.cs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Abstractions.Constants;
|
||||||
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
|
using BTCPayServer.Abstractions.Models;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Controllers;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.HostedServices;
|
||||||
|
using BTCPayServer.Models.WalletViewModels;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Rating;
|
||||||
|
using BTCPayServer.Services.Rates;
|
||||||
|
using BTCPayServer.Services.Stores;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using NBitcoin;
|
||||||
|
using NBitcoin.DataEncoders;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.Vouchers;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class VoucherController : Controller
|
||||||
|
{
|
||||||
|
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||||
|
private readonly UIStorePullPaymentsController _uiStorePullPaymentsController;
|
||||||
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||||
|
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||||
|
private readonly StoreRepository _storeRepository;
|
||||||
|
private readonly RateFetcher _rateFetcher;
|
||||||
|
private readonly BTCPayNetworkProvider _networkProvider;
|
||||||
|
|
||||||
|
public VoucherController(PullPaymentHostedService pullPaymentHostedService,
|
||||||
|
UIStorePullPaymentsController uiStorePullPaymentsController,
|
||||||
|
ApplicationDbContextFactory dbContextFactory,
|
||||||
|
IEnumerable< IPayoutHandler> payoutHandlers, StoreRepository storeRepository, RateFetcher rateFetcher, BTCPayNetworkProvider networkProvider )
|
||||||
|
{
|
||||||
|
_pullPaymentHostedService = pullPaymentHostedService;
|
||||||
|
_uiStorePullPaymentsController = uiStorePullPaymentsController;
|
||||||
|
_dbContextFactory = dbContextFactory;
|
||||||
|
_payoutHandlers = payoutHandlers;
|
||||||
|
_storeRepository = storeRepository;
|
||||||
|
_rateFetcher = rateFetcher;
|
||||||
|
_networkProvider = networkProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("~/plugins/{storeId}/vouchers")]
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
public async Task<IActionResult> ListVouchers(string storeId)
|
||||||
|
{
|
||||||
|
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
await using var ctx = _dbContextFactory.CreateContext();
|
||||||
|
var ppsQuery = await ctx.PullPayments
|
||||||
|
.Include(data => data.Payouts)
|
||||||
|
.Where(p => p.StoreId == storeId && p.Archived == false)
|
||||||
|
.OrderByDescending(data => data.Id).ToListAsync();
|
||||||
|
|
||||||
|
var vouchers = ppsQuery.Select(pp => (PullPayment: pp, Blob: pp.GetBlob())).Where(blob => blob.Blob.Name.StartsWith("Voucher")).ToList();
|
||||||
|
|
||||||
|
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
|
||||||
|
if (!paymentMethods.Any())
|
||||||
|
{
|
||||||
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||||
|
{
|
||||||
|
Message = "You must enable at least one payment method before creating a voucher.",
|
||||||
|
Severity = StatusMessageModel.StatusSeverity.Error
|
||||||
|
});
|
||||||
|
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new {storeId});
|
||||||
|
}
|
||||||
|
return View( vouchers.Select(tuple => new VoucherViewModel()
|
||||||
|
{
|
||||||
|
Amount = tuple.Blob.Limit,
|
||||||
|
Currency = tuple.Blob.Currency,
|
||||||
|
Id = tuple.PullPayment.Id,
|
||||||
|
Name = tuple.Blob.Name,
|
||||||
|
PaymentMethods = tuple.Blob.SupportedPaymentMethods,
|
||||||
|
Progress = _pullPaymentHostedService.CalculatePullPaymentProgress(tuple.PullPayment, now)
|
||||||
|
}).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("~/plugins/{storeId}/vouchers/create")]
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
public async Task<IActionResult> CreateVoucher(string storeId)
|
||||||
|
{
|
||||||
|
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
|
||||||
|
if (!paymentMethods.Any())
|
||||||
|
{
|
||||||
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||||
|
{
|
||||||
|
Message = "You must enable at least one payment method before creating a voucher.",
|
||||||
|
Severity = StatusMessageModel.StatusSeverity.Error
|
||||||
|
});
|
||||||
|
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new {storeId});
|
||||||
|
}
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("~/plugins/{storeId}/vouchers/create")]
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
public async Task<IActionResult> CreateVoucher(string storeId, decimal amount, string currency)
|
||||||
|
{
|
||||||
|
ModelState.Clear();
|
||||||
|
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
|
||||||
|
if (!paymentMethods.Any())
|
||||||
|
{
|
||||||
|
|
||||||
|
TempData[WellKnownTempData.ErrorMessage] = "You must enable at least one payment method before creating a voucher.";
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new {storeId});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount <= 0)
|
||||||
|
{
|
||||||
|
TempData[WellKnownTempData.ErrorMessage] = "Amount must be greater than 0";
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
var storeBLob = HttpContext.GetStoreData().GetStoreBlob();
|
||||||
|
currency??=storeBLob.DefaultCurrency;
|
||||||
|
|
||||||
|
var rate = await _rateFetcher.FetchRate(new CurrencyPair("BTC", currency),
|
||||||
|
storeBLob.GetRateRules(_networkProvider), CancellationToken.None);
|
||||||
|
if (rate.BidAsk == null)
|
||||||
|
{
|
||||||
|
|
||||||
|
TempData[WellKnownTempData.ErrorMessage] = "Currency is not supported";
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
string description = string.Empty;
|
||||||
|
if (currency!= "BTC")
|
||||||
|
{
|
||||||
|
description = $"{amount} {currency} voucher redeemable for {amount * rate.BidAsk.Bid} BTC";
|
||||||
|
}
|
||||||
|
|
||||||
|
var pp = await _pullPaymentHostedService.CreatePullPayment(new CreatePullPayment()
|
||||||
|
{
|
||||||
|
Amount = amount* rate.BidAsk.Bid,
|
||||||
|
Currency = "BTC",
|
||||||
|
Name = "Voucher " + Encoders.Base58.EncodeData(RandomUtils.GetBytes(6)),
|
||||||
|
Description = description,
|
||||||
|
StoreId = storeId,
|
||||||
|
PaymentMethodIds = paymentMethods.ToArray(),
|
||||||
|
AutoApproveClaims = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(View), new {id = pp});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("~/plugins/vouchers/{id}")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<IActionResult> View(string id)
|
||||||
|
{
|
||||||
|
await using var ctx = _dbContextFactory.CreateContext();
|
||||||
|
var pp = await ctx.PullPayments
|
||||||
|
.Include(data => data.Payouts)
|
||||||
|
.SingleOrDefaultAsync(p => p.Id == id && p.Archived == false);
|
||||||
|
|
||||||
|
if (pp == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var blob = pp.GetBlob();
|
||||||
|
if (!blob.Name.StartsWith("Voucher"))
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
var store = await _storeRepository.FindStore(pp.StoreId);
|
||||||
|
var storeBlob = store.GetStoreBlob();
|
||||||
|
var progress = _pullPaymentHostedService.CalculatePullPaymentProgress(pp, now);
|
||||||
|
return View(new VoucherViewModel()
|
||||||
|
{
|
||||||
|
Amount = blob.Limit,
|
||||||
|
Currency = blob.Currency,
|
||||||
|
Id = pp.Id,
|
||||||
|
Name = blob.Name,
|
||||||
|
PaymentMethods = blob.SupportedPaymentMethods,
|
||||||
|
Progress = progress,
|
||||||
|
StoreName = store.StoreName,
|
||||||
|
BrandColor = storeBlob.BrandColor,
|
||||||
|
CssFileId = storeBlob.CssFileId,
|
||||||
|
LogoFileId = storeBlob.LogoFileId,
|
||||||
|
SupportsLNURL = _pullPaymentHostedService.SupportsLNURL(blob),
|
||||||
|
Description = blob.Description
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class VoucherViewModel
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
public string Currency { get; set; }
|
||||||
|
public PaymentMethodId[] PaymentMethods { get; set; }
|
||||||
|
public PullPaymentsModel.PullPaymentModel.ProgressModel Progress { get; set; }
|
||||||
|
public string StoreName { get; set; }
|
||||||
|
public string LogoFileId { get; set; }
|
||||||
|
public string BrandColor { get; set; }
|
||||||
|
public string CssFileId { get; set; }
|
||||||
|
public bool SupportsLNURL { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@using BTCPayServer.Abstractions.Services
|
||||||
|
@inject Safe Safe
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
@addTagHelper *, BTCPayServer
|
||||||
|
@addTagHelper *, BTCPayServer.Abstractions
|
||||||
@@ -1,14 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Contracts;
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
using BTCPayServer.Abstractions.Models;
|
using BTCPayServer.Abstractions.Models;
|
||||||
using BTCPayServer.Abstractions.Services;
|
using BTCPayServer.Abstractions.Services;
|
||||||
using BTCPayServer.Common;
|
using BTCPayServer.Common;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.PayoutProcessors;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using WalletWasabi.Backend.Controllers;
|
using WalletWasabi.Backend.Controllers;
|
||||||
@@ -77,7 +85,7 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin
|
|||||||
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Wabisabi/WabisabiWalletSend",
|
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Wabisabi/WabisabiWalletSend",
|
||||||
"onchain-wallet-send"));
|
"onchain-wallet-send"));
|
||||||
|
|
||||||
// applicationBuilder.AddSingleton<IPayoutProcessorFactory, WabisabiPayoutProcessor>();
|
applicationBuilder.AddSingleton<IPayoutProcessorFactory, WabisabiPayoutProcessor>();
|
||||||
Logger.SetMinimumLevel(LogLevel.Info);
|
Logger.SetMinimumLevel(LogLevel.Info);
|
||||||
Logger.SetModes(LogMode.DotNetLoggers);
|
Logger.SetModes(LogMode.DotNetLoggers);
|
||||||
|
|
||||||
@@ -85,50 +93,51 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin
|
|||||||
base.Execute(applicationBuilder);
|
base.Execute(applicationBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public class WabisabiPayoutProcessor: IPayoutProcessorFactory
|
public class WabisabiPayoutProcessor: IPayoutProcessorFactory
|
||||||
// {
|
{
|
||||||
// private readonly LinkGenerator _linkGenerator;
|
private readonly LinkGenerator _linkGenerator;
|
||||||
//
|
|
||||||
// public WabisabiPayoutProcessor(LinkGenerator linkGenerator)
|
public WabisabiPayoutProcessor(LinkGenerator linkGenerator)
|
||||||
// {
|
{
|
||||||
// _linkGenerator = linkGenerator;
|
_linkGenerator = linkGenerator;
|
||||||
// }
|
}
|
||||||
// public string Processor { get; } = "Wabisabi";
|
public string Processor { get; } = "Wabisabi";
|
||||||
// public string FriendlyName { get; } = "Coinjoin";
|
public string FriendlyName { get; } = "Coinjoin";
|
||||||
// public string ConfigureLink(string storeId, PaymentMethodId paymentMethodId, HttpRequest request)
|
public string ConfigureLink(string storeId, PaymentMethodId paymentMethodId, HttpRequest request)
|
||||||
// {
|
{
|
||||||
// return _linkGenerator.GetUriByAction(
|
return _linkGenerator.GetUriByAction(
|
||||||
// nameof(WabisabiStoreController.UpdateWabisabiStoreSettings),
|
nameof(WabisabiStoreController.UpdateWabisabiStoreSettings),
|
||||||
// "WabisabiStore",
|
"WabisabiStore",
|
||||||
// new { storeId},
|
new { storeId},
|
||||||
// request.Scheme,
|
request.Scheme,
|
||||||
// request.Host,
|
request.Host,
|
||||||
// request.PathBase);
|
request.PathBase);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// public IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
public IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
||||||
// {
|
{
|
||||||
// return new[] {new PaymentMethodId("BTC", BitcoinPaymentType.Instance)};
|
return new[] {new PaymentMethodId("BTC", BitcoinPaymentType.Instance)};
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// public Task<IHostedService> ConstructProcessor(PayoutProcessorData settings)
|
public async Task<IHostedService> ConstructProcessor(PayoutProcessorData settings)
|
||||||
// {
|
{
|
||||||
// return Task.FromResult<IHostedService>(new ShellSerice());
|
return new ShellSerice();
|
||||||
// }
|
}
|
||||||
// public class ShellSerice:IHostedService
|
|
||||||
// {
|
public class ShellSerice:IHostedService
|
||||||
// public Task StartAsync(CancellationToken cancellationToken)
|
{
|
||||||
// {
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
// return Task.CompletedTask;
|
{
|
||||||
// }
|
return Task.CompletedTask;
|
||||||
//
|
}
|
||||||
// public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
// {
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
// return Task.CompletedTask;
|
{
|
||||||
// }
|
return Task.CompletedTask;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
//
|
}
|
||||||
|
|
||||||
|
|
||||||
public override void Execute(IApplicationBuilder applicationBuilder,
|
public override void Execute(IApplicationBuilder applicationBuilder,
|
||||||
IServiceProvider applicationBuilderApplicationServices)
|
IServiceProvider applicationBuilderApplicationServices)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using BTCPayServer.Data;
|
|||||||
using BTCPayServer.PayoutProcessors;
|
using BTCPayServer.PayoutProcessors;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using NBitcoin;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Plugins.Wabisabi
|
namespace BTCPayServer.Plugins.Wabisabi
|
||||||
@@ -19,40 +18,49 @@ namespace BTCPayServer.Plugins.Wabisabi
|
|||||||
private readonly WalletProvider _walletProvider;
|
private readonly WalletProvider _walletProvider;
|
||||||
private readonly WalletRepository _walletRepository;
|
private readonly WalletRepository _walletRepository;
|
||||||
private readonly IMemoryCache _memoryCache;
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
private readonly PayoutProcessorService _payoutProcessorService;
|
||||||
|
private readonly EventAggregator _eventAggregator;
|
||||||
private string[] _ids => _coordinatorClientInstanceManager.HostedServices.Keys.ToArray();
|
private string[] _ids => _coordinatorClientInstanceManager.HostedServices.Keys.ToArray();
|
||||||
|
|
||||||
public WabisabiService(IStoreRepository storeRepository,
|
public WabisabiService(IStoreRepository storeRepository,
|
||||||
WabisabiCoordinatorClientInstanceManager coordinatorClientInstanceManager,
|
WabisabiCoordinatorClientInstanceManager coordinatorClientInstanceManager,
|
||||||
WalletProvider walletProvider,
|
WalletProvider walletProvider,
|
||||||
WalletRepository walletRepository,
|
WalletRepository walletRepository,
|
||||||
IMemoryCache memoryCache)
|
IMemoryCache memoryCache, PayoutProcessorService payoutProcessorService, EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
_coordinatorClientInstanceManager = coordinatorClientInstanceManager;
|
_coordinatorClientInstanceManager = coordinatorClientInstanceManager;
|
||||||
_walletProvider = walletProvider;
|
_walletProvider = walletProvider;
|
||||||
_walletRepository = walletRepository;
|
_walletRepository = walletRepository;
|
||||||
_memoryCache = memoryCache;
|
_memoryCache = memoryCache;
|
||||||
|
_payoutProcessorService = payoutProcessorService;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetCacheKey(string storeId)
|
public static string GetCacheKey(string storeId)
|
||||||
{
|
{
|
||||||
return $"{nameof(WabisabiStoreSettings)}-{storeId}";
|
return $"{nameof(WabisabiStoreSettings)}-{storeId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<WabisabiStoreSettings> GetWabisabiForStore(string storeId)
|
public async Task<WabisabiStoreSettings> GetWabisabiForStore(string storeId)
|
||||||
{
|
{
|
||||||
|
|
||||||
var res = await _memoryCache.GetOrCreate(GetCacheKey(storeId), async entry =>
|
var res = await _memoryCache.GetOrCreate(GetCacheKey(storeId), async entry =>
|
||||||
{
|
{
|
||||||
var res = await _storeRepository.GetSettingAsync<WabisabiStoreSettings>(storeId, nameof(WabisabiStoreSettings));
|
var res = await _storeRepository.GetSettingAsync<WabisabiStoreSettings>(storeId,
|
||||||
|
nameof(WabisabiStoreSettings));
|
||||||
res ??= new WabisabiStoreSettings();
|
res ??= new WabisabiStoreSettings();
|
||||||
res.Settings = res.Settings.Where(settings => _ids.Contains(settings.Coordinator)).ToList();
|
res.Settings = res.Settings.Where(settings => _ids.Contains(settings.Coordinator)).ToList();
|
||||||
res.Settings.ForEach(settings =>
|
res.Settings.ForEach(settings =>
|
||||||
{
|
{
|
||||||
if(settings.RoundWhenEnabled != null && string.IsNullOrEmpty(settings.RoundWhenEnabled.PlebsDontPayThreshold))
|
if (settings.RoundWhenEnabled != null &&
|
||||||
|
string.IsNullOrEmpty(settings.RoundWhenEnabled.PlebsDontPayThreshold))
|
||||||
{
|
{
|
||||||
settings.RoundWhenEnabled.PlebsDontPayThreshold = "1000000";
|
settings.RoundWhenEnabled.PlebsDontPayThreshold = "1000000";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
await EnablePayoutProcessorBasedOnSettings(storeId, res);
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,7 +78,32 @@ namespace BTCPayServer.Plugins.Wabisabi
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetWabisabiForStore(string storeId, WabisabiStoreSettings wabisabiSettings, string termsCoord = null)
|
private async Task EnablePayoutProcessorBasedOnSettings(string storeId, WabisabiStoreSettings res)
|
||||||
|
{
|
||||||
|
var paybatching = res.Settings.Any(settings => settings.Enabled) && res.Active && (res.PlebMode ||res.BatchPayments);
|
||||||
|
var existingProcessor = (await _payoutProcessorService.GetProcessors(
|
||||||
|
new PayoutProcessorService.PayoutProcessorQuery()
|
||||||
|
{
|
||||||
|
Stores = new[] {storeId},
|
||||||
|
Processors = new[] {"Wabisabi"},
|
||||||
|
|
||||||
|
})).FirstOrDefault();
|
||||||
|
|
||||||
|
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||||
|
{
|
||||||
|
Id = existingProcessor?.Id,
|
||||||
|
Data = paybatching? new PayoutProcessorData()
|
||||||
|
{
|
||||||
|
Id = existingProcessor?.Id,
|
||||||
|
Processor = "Wabisabi",
|
||||||
|
StoreId = storeId,
|
||||||
|
PaymentMethod = "BTC",
|
||||||
|
}: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetWabisabiForStore(string storeId, WabisabiStoreSettings wabisabiSettings,
|
||||||
|
string termsCoord = null)
|
||||||
{
|
{
|
||||||
foreach (var setting in wabisabiSettings.Settings.Where(setting => !setting.Enabled))
|
foreach (var setting in wabisabiSettings.Settings.Where(setting => !setting.Enabled))
|
||||||
{
|
{
|
||||||
@@ -82,51 +115,38 @@ namespace BTCPayServer.Plugins.Wabisabi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = await GetWabisabiForStore(storeId);
|
var res = await GetWabisabiForStore(storeId);
|
||||||
foreach (var wabisabiStoreCoordinatorSettings in wabisabiSettings.Settings)
|
foreach (var wabisabiStoreCoordinatorSettings in wabisabiSettings.Settings)
|
||||||
|
{
|
||||||
|
if (!wabisabiStoreCoordinatorSettings.Enabled)
|
||||||
{
|
{
|
||||||
if (!wabisabiStoreCoordinatorSettings.Enabled)
|
wabisabiStoreCoordinatorSettings.RoundWhenEnabled = null;
|
||||||
{
|
|
||||||
wabisabiStoreCoordinatorSettings.RoundWhenEnabled = null;
|
|
||||||
}else if (
|
|
||||||
(termsCoord == wabisabiStoreCoordinatorSettings.Coordinator ||
|
|
||||||
res?.Settings?.Find(settings =>
|
|
||||||
settings.Coordinator == wabisabiStoreCoordinatorSettings.Coordinator)?.RoundWhenEnabled is null) &&
|
|
||||||
_coordinatorClientInstanceManager.HostedServices.TryGetValue(wabisabiStoreCoordinatorSettings.Coordinator, out var coordinator))
|
|
||||||
{
|
|
||||||
var round = coordinator.RoundStateUpdater.RoundStates.LastOrDefault();
|
|
||||||
wabisabiStoreCoordinatorSettings.RoundWhenEnabled = new LastCoordinatorRoundConfig()
|
|
||||||
{
|
|
||||||
CoordinationFeeRate = round.Value.CoinjoinState.Parameters.CoordinationFeeRate.Rate,
|
|
||||||
PlebsDontPayThreshold = round.Value.CoinjoinState.Parameters.CoordinationFeeRate
|
|
||||||
.PlebsDontPayThreshold.Satoshi.ToString(),
|
|
||||||
MinInputCountByRound = round.Value.CoinjoinState.Parameters.MinInputCountByRound,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
await _storeRepository.UpdateSetting(storeId, nameof(WabisabiStoreSettings), wabisabiSettings!);
|
else if (
|
||||||
|
(termsCoord == wabisabiStoreCoordinatorSettings.Coordinator ||
|
||||||
|
res?.Settings?.Find(settings =>
|
||||||
|
settings.Coordinator == wabisabiStoreCoordinatorSettings.Coordinator)
|
||||||
|
?.RoundWhenEnabled is null) &&
|
||||||
|
_coordinatorClientInstanceManager.HostedServices.TryGetValue(
|
||||||
|
wabisabiStoreCoordinatorSettings.Coordinator, out var coordinator))
|
||||||
|
{
|
||||||
|
var round = coordinator.RoundStateUpdater.RoundStates.LastOrDefault();
|
||||||
|
wabisabiStoreCoordinatorSettings.RoundWhenEnabled = new LastCoordinatorRoundConfig()
|
||||||
|
{
|
||||||
|
CoordinationFeeRate = round.Value.CoinjoinState.Parameters.CoordinationFeeRate.Rate,
|
||||||
|
PlebsDontPayThreshold = round.Value.CoinjoinState.Parameters.CoordinationFeeRate
|
||||||
|
.PlebsDontPayThreshold.Satoshi.ToString(),
|
||||||
|
MinInputCountByRound = round.Value.CoinjoinState.Parameters.MinInputCountByRound,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _storeRepository.UpdateSetting(storeId, nameof(WabisabiStoreSettings), wabisabiSettings!);
|
||||||
|
|
||||||
_memoryCache.Remove(GetCacheKey(storeId));
|
_memoryCache.Remove(GetCacheKey(storeId));
|
||||||
await _walletProvider.SettingsUpdated(storeId, wabisabiSettings);
|
await _walletProvider.SettingsUpdated(storeId, wabisabiSettings);
|
||||||
// var existingProcessor = (await _payoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery()
|
|
||||||
// {
|
await EnablePayoutProcessorBasedOnSettings(storeId, wabisabiSettings);
|
||||||
// Stores = new[] {storeId},
|
|
||||||
// Processors = new[] {"Wabisabi"},
|
|
||||||
//
|
|
||||||
// })).FirstOrDefault();
|
|
||||||
// _eventAggregator.Publish(new PayoutProcessorUpdated()
|
|
||||||
// {
|
|
||||||
// Id = existingProcessor?.Id,
|
|
||||||
// Data = paybatching? new PayoutProcessorData()
|
|
||||||
// {
|
|
||||||
// Id = existingProcessor?.Id,
|
|
||||||
// Processor = "Wabisabi",
|
|
||||||
// StoreId = storeId,
|
|
||||||
// PaymentMethod = "BTC",
|
|
||||||
// }: null
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -141,7 +161,7 @@ namespace BTCPayServer.Plugins.Wabisabi
|
|||||||
return await _memoryCache.GetOrCreateAsync(k, async entry =>
|
return await _memoryCache.GetOrCreateAsync(k, async entry =>
|
||||||
{
|
{
|
||||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
|
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
|
||||||
var result = (await _walletRepository.GetWalletObjects(
|
var result = (await _walletRepository.GetWalletObjects(
|
||||||
new GetWalletObjectsQuery(new WalletId(storeId, "BTC"))
|
new GetWalletObjectsQuery(new WalletId(storeId, "BTC"))
|
||||||
{
|
{
|
||||||
Type = "coinjoin"
|
Type = "coinjoin"
|
||||||
@@ -152,8 +172,6 @@ namespace BTCPayServer.Plugins.Wabisabi
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user