mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Make Checkout Cheat Mode extensible by plugins (#6543)
This commit is contained in:
parent
0754a809e7
commit
5536935ff8
9 changed files with 224 additions and 197 deletions
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -9,6 +11,7 @@ using BTCPayServer.Payments;
|
|||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
|
||||
|
@ -25,116 +28,101 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
public class MineBlocksRequest
|
||||
{
|
||||
public string PaymentMethodId { get; set; }
|
||||
public int BlockCount { get; set; } = 1;
|
||||
public string CryptoCode { get; set; } = "BTC";
|
||||
}
|
||||
|
||||
[HttpPost("i/{invoiceId}/test-payment")]
|
||||
[CheatModeRoute]
|
||||
public async Task<IActionResult> TestPayment(string invoiceId, FakePaymentRequest request,
|
||||
[FromServices] Cheater cheater,
|
||||
[FromServices] LightningClientFactoryService lightningClientFactoryService)
|
||||
public async Task<IActionResult> TestPayment(string invoiceId, FakePaymentRequest request,
|
||||
[FromServices] IEnumerable<ICheckoutCheatModeExtension> extensions)
|
||||
{
|
||||
var isSats = request.CryptoCode.ToUpper(CultureInfo.InvariantCulture) == "SATS";
|
||||
var amount = isSats ? new Money(request.Amount, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC) : request.Amount;
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
var isSats = request.CryptoCode.ToUpper(CultureInfo.InvariantCulture) == "SATS";
|
||||
var cryptoCode = isSats ? "BTC" : request.CryptoCode;
|
||||
var amount = new Money(request.Amount, isSats ? MoneyUnit.Satoshi : MoneyUnit.BTC);
|
||||
var btcpayNetwork = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var network = btcpayNetwork.NBitcoinNetwork;
|
||||
var paymentMethodId = new[] { store.GetDefaultPaymentId() }
|
||||
.Concat(store.GetEnabledPaymentIds())
|
||||
.FirstOrDefault(p => p?.ToString() == request.PaymentMethodId);
|
||||
|
||||
try
|
||||
PaymentMethodId paymentMethodId = GetPaymentMethodId(request.PaymentMethodId, store);
|
||||
var paymentMethod = invoice.GetPaymentPrompt(paymentMethodId);
|
||||
var extension = GetCheatModeExtension(extensions, paymentMethodId);
|
||||
var details = _handlers.ParsePaymentPromptDetails(paymentMethod);
|
||||
if (extension is not null)
|
||||
{
|
||||
var paymentMethod = invoice.GetPaymentPrompt(paymentMethodId);
|
||||
var details = _handlers.ParsePaymentPromptDetails(paymentMethod);
|
||||
var destination = paymentMethod?.Destination;
|
||||
|
||||
if (details is BitcoinPaymentPromptDetails)
|
||||
try
|
||||
{
|
||||
var address = BitcoinAddress.Create(destination, network);
|
||||
var txid = (await cheater.GetCashCow(cryptoCode).SendToAddressAsync(address, amount)).ToString();
|
||||
|
||||
var result = await extension.PayInvoice(new ICheckoutCheatModeExtension.PayInvoiceContext(
|
||||
invoice,
|
||||
amount,
|
||||
store,
|
||||
paymentMethod,
|
||||
details));
|
||||
return Ok(new
|
||||
{
|
||||
Txid = txid,
|
||||
AmountRemaining = paymentMethod.Calculate().Due - amount.ToDecimal(MoneyUnit.BTC),
|
||||
SuccessMessage = $"Created transaction {txid}"
|
||||
Txid = result.TransactionId,
|
||||
AmountRemaining = result.AmountRemaining ?? paymentMethod.Calculate().Due - amount,
|
||||
SuccessMessage = result.SuccessMessage ?? $"Created transaction {result.TransactionId}"
|
||||
});
|
||||
}
|
||||
else if (details is LigthningPaymentPromptDetails)
|
||||
catch (Exception e)
|
||||
{
|
||||
// requires the channels to be set up using the BTCPayServer.Tests/docker-lightning-channel-setup.sh script
|
||||
var lnClient = lightningClientFactoryService.Create(
|
||||
Environment.GetEnvironmentVariable("BTCPAY_BTCEXTERNALLNDREST"),
|
||||
btcpayNetwork);
|
||||
|
||||
var lnAmount = new LightMoney(amount.Satoshi, LightMoneyUnit.Satoshi);
|
||||
var response = await lnClient.Pay(destination, new PayInvoiceParams { Amount = lnAmount });
|
||||
|
||||
if (response.Result == PayResult.Ok)
|
||||
return BadRequest(new
|
||||
{
|
||||
var bolt11 = BOLT11PaymentRequest.Parse(destination, network);
|
||||
var paymentHash = bolt11.PaymentHash?.ToString();
|
||||
var paid = response.Details.TotalAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||
return Ok(new
|
||||
{
|
||||
Txid = paymentHash,
|
||||
AmountRemaining = paymentMethod.Calculate().TotalDue - paid,
|
||||
SuccessMessage = $"Sent payment {paymentHash}"
|
||||
});
|
||||
}
|
||||
return UnprocessableEntity(new
|
||||
{
|
||||
ErrorMessage = response.ErrorDetail
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return UnprocessableEntity(new
|
||||
{
|
||||
ErrorMessage = $"Payment method {paymentMethodId} is not supported"
|
||||
ErrorMessage = e.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
else
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
ErrorMessage = e.Message
|
||||
});
|
||||
return BadRequest(new { ErrorMessage = "No ICheatModeExtension registered for this payment method" });
|
||||
}
|
||||
}
|
||||
|
||||
private static ICheckoutCheatModeExtension GetCheatModeExtension(IEnumerable<ICheckoutCheatModeExtension> extensions, PaymentMethodId paymentMethodId)
|
||||
{
|
||||
return extensions.Where(e => e.Handle(paymentMethodId)).FirstOrDefault();
|
||||
}
|
||||
|
||||
private static PaymentMethodId GetPaymentMethodId(string requestPmi, StoreData store)
|
||||
{
|
||||
return new[] { store.GetDefaultPaymentId() }
|
||||
.Concat(store.GetEnabledPaymentIds())
|
||||
.FirstOrDefault(p => p?.ToString() == requestPmi);
|
||||
}
|
||||
|
||||
[HttpPost("i/{invoiceId}/mine-blocks")]
|
||||
[CheatModeRoute]
|
||||
public IActionResult MineBlock(string invoiceId, MineBlocksRequest request, [FromServices] Cheater cheater)
|
||||
public async Task<IActionResult> MineBlock(string invoiceId, MineBlocksRequest request, [FromServices] IEnumerable<ICheckoutCheatModeExtension> extensions)
|
||||
{
|
||||
var blockRewardBitcoinAddress = cheater.GetCashCow(request.CryptoCode).GetNewAddress();
|
||||
try
|
||||
{
|
||||
if (request.BlockCount > 0)
|
||||
{
|
||||
cheater.GetCashCow(request.CryptoCode).GenerateToAddress(request.BlockCount, blockRewardBitcoinAddress);
|
||||
return Ok(new { SuccessMessage = $"Mined {request.BlockCount} block{(request.BlockCount == 1 ? "" : "s")} " });
|
||||
}
|
||||
if (request.BlockCount <= 0)
|
||||
return BadRequest(new { ErrorMessage = "Number of blocks should be at least 1" });
|
||||
}
|
||||
catch (Exception e)
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
var paymentMethodId = GetPaymentMethodId(request.PaymentMethodId, store);
|
||||
var extension = GetCheatModeExtension(extensions, paymentMethodId);
|
||||
if (extension != null)
|
||||
{
|
||||
return BadRequest(new { ErrorMessage = e.Message });
|
||||
try
|
||||
{
|
||||
var result = await extension.MineBlock(new() { BlockCount = request.BlockCount });
|
||||
var defaultMessage = $"Mined {request.BlockCount} block{(request.BlockCount == 1 ? "" : "s")} ";
|
||||
return Ok(new { SuccessMessage = result.SuccessMessage ?? defaultMessage });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return BadRequest(new { ErrorMessage = e.Message });
|
||||
}
|
||||
}
|
||||
else
|
||||
return BadRequest(new { ErrorMessage = "No ICheatModeExtension registered for this payment method" });
|
||||
}
|
||||
|
||||
[HttpPost("i/{invoiceId}/expire")]
|
||||
[CheatModeRoute]
|
||||
public async Task<IActionResult> Expire(string invoiceId, int seconds, [FromServices] Cheater cheater)
|
||||
public async Task<IActionResult> Expire(string invoiceId, int seconds)
|
||||
{
|
||||
try
|
||||
{
|
||||
await cheater.UpdateInvoiceExpiry(invoiceId, TimeSpan.FromSeconds(seconds));
|
||||
await _InvoiceRepository.UpdateInvoiceExpiry(invoiceId, TimeSpan.FromSeconds(seconds));
|
||||
return Ok(new { SuccessMessage = $"Invoice set to expire in {seconds} seconds." });
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
@ -642,6 +642,9 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
|||
services.AddSingleton<IPaymentMethodBitpayAPIExtension>(provider =>
|
||||
(IPaymentMethodBitpayAPIExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentMethodBitpayAPIExtension), new object[] { pmi }));
|
||||
|
||||
services.AddSingleton<ICheckoutCheatModeExtension>(provider =>
|
||||
(ICheckoutCheatModeExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinCheckoutCheatModeExtension), new object[] { network }));
|
||||
|
||||
if (!network.ReadonlyWallet && network.WalletSupported)
|
||||
{
|
||||
var payoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId(network.CryptoCode);
|
||||
|
@ -669,6 +672,8 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
|||
var payoutMethodId = PayoutTypes.LN.GetPayoutMethodId(network.CryptoCode);
|
||||
services.AddSingleton<IPayoutHandler>(provider =>
|
||||
(IPayoutHandler)ActivatorUtilities.CreateInstance(provider, typeof(LightningLikePayoutHandler), new object[] { payoutMethodId, network }));
|
||||
services.AddSingleton<ICheckoutCheatModeExtension>(provider =>
|
||||
(ICheckoutCheatModeExtension)ActivatorUtilities.CreateInstance(provider, typeof(LightningCheckoutCheatModeExtension), new object[] { network }));
|
||||
}
|
||||
// LNURL
|
||||
{
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services;
|
||||
using NBitcoin;
|
||||
using ExchangeSharp.BinanceGroup;
|
||||
|
||||
namespace BTCPayServer.Payments.Bitcoin
|
||||
{
|
||||
public class BitcoinCheckoutCheatModeExtension : ICheckoutCheatModeExtension
|
||||
{
|
||||
private readonly Cheater _cheater;
|
||||
|
||||
public BitcoinCheckoutCheatModeExtension(Cheater cheater, BTCPayNetwork network)
|
||||
{
|
||||
_cheater = cheater;
|
||||
Network = network;
|
||||
pmi = PaymentTypes.CHAIN.GetPaymentMethodId(Network.CryptoCode);
|
||||
}
|
||||
public BTCPayNetwork Network { get; }
|
||||
|
||||
private PaymentMethodId pmi;
|
||||
|
||||
public bool Handle(PaymentMethodId paymentMethodId)
|
||||
=> paymentMethodId == pmi;
|
||||
|
||||
public async Task<ICheckoutCheatModeExtension.MineBlockResult> MineBlock(ICheckoutCheatModeExtension.MineBlockContext mineBlockContext)
|
||||
{
|
||||
var cow = _cheater.GetCashCow(Network.CryptoCode);
|
||||
var blockRewardBitcoinAddress = await cow.GetNewAddressAsync();
|
||||
await cow.GenerateToAddressAsync(mineBlockContext.BlockCount, blockRewardBitcoinAddress);
|
||||
return new ICheckoutCheatModeExtension.MineBlockResult();
|
||||
}
|
||||
|
||||
public async Task<ICheckoutCheatModeExtension.PayInvoiceResult> PayInvoice(ICheckoutCheatModeExtension.PayInvoiceContext payInvoiceContext)
|
||||
{
|
||||
var address = BitcoinAddress.Create(payInvoiceContext.PaymentPrompt.Destination, Network.NBitcoinNetwork);
|
||||
var txid = (await _cheater.GetCashCow(Network.CryptoCode).SendToAddressAsync(address, new Money(payInvoiceContext.Amount, MoneyUnit.BTC))).ToString();
|
||||
return new ICheckoutCheatModeExtension.PayInvoiceResult(txid);
|
||||
}
|
||||
}
|
||||
}
|
57
BTCPayServer/Payments/ICheckoutCheatModeExtension.cs
Normal file
57
BTCPayServer/Payments/ICheckoutCheatModeExtension.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
#nullable enable
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
public interface ICheckoutCheatModeExtension
|
||||
{
|
||||
public class PayInvoiceResult
|
||||
{
|
||||
public PayInvoiceResult(string transactionId)
|
||||
{
|
||||
TransactionId = transactionId;
|
||||
}
|
||||
public string TransactionId { get; set; }
|
||||
public decimal? AmountRemaining { get; set; }
|
||||
public string? SuccessMessage { get; set; }
|
||||
}
|
||||
public class MineBlockResult
|
||||
{
|
||||
public MineBlockResult()
|
||||
{
|
||||
}
|
||||
public MineBlockResult(string? successMessage)
|
||||
{
|
||||
SuccessMessage = successMessage;
|
||||
}
|
||||
public string? SuccessMessage { get; set; }
|
||||
}
|
||||
public class MineBlockContext
|
||||
{
|
||||
public int BlockCount { get; set; }
|
||||
}
|
||||
public class PayInvoiceContext
|
||||
{
|
||||
public PayInvoiceContext(InvoiceEntity invoice, decimal amount, StoreData store, PaymentPrompt paymentMethod, object details)
|
||||
{
|
||||
this.Invoice = invoice;
|
||||
this.Amount = amount;
|
||||
this.Store = store;
|
||||
PaymentPrompt = paymentMethod;
|
||||
PaymentPromptDetails = details;
|
||||
}
|
||||
|
||||
public InvoiceEntity Invoice { get; }
|
||||
public decimal Amount { get; }
|
||||
public StoreData Store { get; }
|
||||
public PaymentPrompt PaymentPrompt { get; }
|
||||
public object? PaymentPromptDetails { get; }
|
||||
}
|
||||
public bool Handle(PaymentMethodId paymentMethodId);
|
||||
Task<PayInvoiceResult> PayInvoice(PayInvoiceContext payInvoiceContext);
|
||||
Task<MineBlockResult> MineBlock(MineBlockContext mineBlockContext);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using static BTCPayServer.Payments.ICheckoutCheatModeExtension;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
public class LightningCheckoutCheatModeExtension : ICheckoutCheatModeExtension
|
||||
{
|
||||
private readonly Cheater _cheater;
|
||||
private readonly LightningClientFactoryService _lightningClientFactoryService;
|
||||
private readonly PaymentMethodId[] pmis;
|
||||
|
||||
public LightningCheckoutCheatModeExtension(Cheater cheater, BTCPayNetwork network, LightningClientFactoryService lightningClientFactoryService)
|
||||
{
|
||||
_cheater = cheater;
|
||||
Network = network;
|
||||
_lightningClientFactoryService = lightningClientFactoryService;
|
||||
pmis = [PaymentTypes.LNURL.GetPaymentMethodId(Network.CryptoCode), PaymentTypes.LN.GetPaymentMethodId(Network.CryptoCode)];
|
||||
}
|
||||
public BTCPayNetwork Network { get; }
|
||||
|
||||
public bool Handle(PaymentMethodId paymentMethodId)
|
||||
=> pmis.Contains(paymentMethodId);
|
||||
|
||||
public Task<ICheckoutCheatModeExtension.MineBlockResult> MineBlock(ICheckoutCheatModeExtension.MineBlockContext mineBlockContext)
|
||||
=> new Bitcoin.BitcoinCheckoutCheatModeExtension(_cheater, Network).MineBlock(mineBlockContext);
|
||||
|
||||
public async Task<ICheckoutCheatModeExtension.PayInvoiceResult> PayInvoice(ICheckoutCheatModeExtension.PayInvoiceContext payInvoiceContext)
|
||||
{
|
||||
// requires the channels to be set up using the BTCPayServer.Tests/docker-lightning-channel-setup.sh script
|
||||
var lnClient = _lightningClientFactoryService.Create(
|
||||
Environment.GetEnvironmentVariable("BTCPAY_BTCEXTERNALLNDREST"),
|
||||
Network);
|
||||
|
||||
var destination = payInvoiceContext.PaymentPrompt.Destination;
|
||||
var lnAmount = new LightMoney(payInvoiceContext.Amount, LightMoneyUnit.BTC);
|
||||
var response = await lnClient.Pay(destination, new PayInvoiceParams { Amount = lnAmount });
|
||||
|
||||
if (response.Result == PayResult.Ok)
|
||||
{
|
||||
var bolt11 = BOLT11PaymentRequest.Parse(destination, Network.NBitcoinNetwork);
|
||||
var paymentHash = bolt11.PaymentHash?.ToString();
|
||||
var paid = response.Details.TotalAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||
return new PayInvoiceResult(paymentHash)
|
||||
{
|
||||
SuccessMessage = $"Sent payment {paymentHash}"
|
||||
};
|
||||
};
|
||||
throw new Exception($"Error while paying through lightning: {(Status: response.Result, ErrorDetails: response.ErrorDetail)}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,11 +27,6 @@ namespace BTCPayServer.Services
|
|||
return _prov.GetExplorerClient(cryptoCode)?.RPCClient;
|
||||
}
|
||||
|
||||
public async Task UpdateInvoiceExpiry(string invoiceId, TimeSpan seconds)
|
||||
{
|
||||
await _invoiceRepository.UpdateInvoiceExpiry(invoiceId, seconds);
|
||||
}
|
||||
|
||||
async Task IHostedService.StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var liquid = _prov.GetNetwork("LBTC");
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<label for="test-payment-amount" class="control-label form-label">Fake a {{cryptoCode}} payment for testing</label>
|
||||
<div class="d-flex gap-2 mb-2">
|
||||
<div class="input-group">
|
||||
<input id="test-payment-amount" name="Amount" type="number" :step="isSats ? '1' : '0.00000001'" min="0" class="form-control" placeholder="@StringLocalizer["Amount"]" v-model="amount" :readonly="paying || paymentMethodId === 'BTC-LN'" />
|
||||
<input id="test-payment-amount" name="Amount" class="form-control" placeholder="@StringLocalizer["Amount"]" v-model="amount" :readonly="paying || paymentMethodId === 'BTC-LN'" />
|
||||
<div id="test-payment-crypto-code" class="input-group-addon input-group-text" v-text="cryptoCode"></div>
|
||||
</div>
|
||||
<button class="btn btn-secondary flex-shrink-0 px-3 w-100px" type="submit" :disabled="paying" id="FakePayment">Pay</button>
|
||||
|
@ -22,6 +22,7 @@
|
|||
<form id="mine-block" :action="`/i/${invoiceId}/mine-blocks`" method="post" v-on:submit.prevent="handleFormSubmit($event, 'mining')" v-if="displayMine">
|
||||
<label for="BlockCount" class="control-label form-label" text-translate="true">Mine to test processing and settlement</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input name="PaymentMethodId" type="hidden" :value="paymentMethodId">
|
||||
<div class="input-group">
|
||||
<input id="BlockCount" name="BlockCount" type="number" step="1" min="1" class="form-control" value="1"/>
|
||||
<div class="input-group-addon input-group-text" text-translate="true">blocks</div>
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
@model CheckoutModel
|
||||
|
||||
<div id="testing">
|
||||
<hr class="my-3" />
|
||||
<p class="alert alert-danger" style="display: none; word-break: break-all;"></p>
|
||||
<p class="alert alert-success" style="display: none; word-break: break-all;"></p>
|
||||
<form id="test-payment" action="/i/@Model.InvoiceId/test-payment" method="post" class="cheat-mode form-inline my-2">
|
||||
<input name="CryptoCode" type="hidden" value="@Model.PaymentMethodCurrency">
|
||||
<div class="form-group mb-1">
|
||||
<label for="test-payment-amount" class="control-label">{{$t("Fake a @Model.PaymentMethodCurrency payment for testing")}}</label>
|
||||
<div class="input-group">
|
||||
<input id="test-payment-amount" name="Amount" type="number" step="0.00000001" min="0" class="form-control" placeholder="@StringLocalizer["Amount"]" value="@Model.Due" />
|
||||
<div id="test-payment-crypto-code" class="input-group-addon">@Model.PaymentMethodCurrency</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="FakePayment" class="btn btn-primary" type="submit">{{$t("Fake Payment")}}</button>
|
||||
<p class="text-muted mt-1">{{$t("This is the same as running bitcoin-cli.sh sendtoaddress xxx")}}</p>
|
||||
</form>
|
||||
<form id="expire-invoice" action="/i/@Model.InvoiceId/expire" method="post" class="mb-1">
|
||||
<button class="btn btn-secondary" type="submit">{{$t("Expire Invoice Now")}}</button>
|
||||
</form>
|
||||
@* TODO
|
||||
<form id="expire-monitoring" action="/i/@Model.InvoiceId/expire-monitoring" method="post" class="mb-1">
|
||||
<!-- TODO only show when expired -->
|
||||
<button class="btn btn-secondary" type="submit">{{$t("Expire Monitoring Now")}} (TODO)</button>
|
||||
</form>
|
||||
*@
|
||||
<form id="mine-block" action="/i/@Model.InvoiceId/mine-blocks" method="post" class="cheat-mode form-inline my-2">
|
||||
<!-- TODO only show when BTC On-chain -->
|
||||
<div class="form-group mb-1">
|
||||
<label for="block-count" class="control-label">{{$t("Mine a few blocks to test processing and settlement.")}}</label>
|
||||
<div class="input-group">
|
||||
<input id="block-count" name="BlockCount" type="number" step="1" min="1" class="form-control" value="1" />
|
||||
<div class="input-group-addon">{{$t("Blocks")}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">{{$t("Mine")}}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
const cheatForms = $('form.cheat-mode');
|
||||
const expireForm = $('form#expire-invoice');
|
||||
|
||||
const successAlert = $('#testing p.alert-success');
|
||||
const errorAlert = $('#testing p.alert-danger');
|
||||
|
||||
cheatForms.submit(e => {
|
||||
e.preventDefault();
|
||||
|
||||
const form = $(e.target);
|
||||
const url = form.attr('action');
|
||||
const data = form.serialize();
|
||||
const inputField = form.find('input[type=number]');
|
||||
const submitButton = form.find('button[type=submit]');
|
||||
|
||||
successAlert.hide();
|
||||
errorAlert.hide();
|
||||
inputField.prop('disabled', true);
|
||||
submitButton.prop('disabled', true);
|
||||
|
||||
$.post({
|
||||
url,
|
||||
data,
|
||||
success(data) {
|
||||
const { successMessage, amountRemaining } = data;
|
||||
successAlert.html(successMessage);
|
||||
successAlert.show();
|
||||
if (amountRemaining){
|
||||
if(amountRemaining > 0) {
|
||||
inputField.val(amountRemaining);
|
||||
} else {
|
||||
form.hide();
|
||||
expireForm.hide();
|
||||
}
|
||||
}
|
||||
},
|
||||
error(xhr) {
|
||||
const { errorMessage } = JSON.parse(xhr.responseText);
|
||||
errorAlert.html(errorMessage).show();
|
||||
},
|
||||
complete() {
|
||||
inputField.prop('disabled', false);
|
||||
submitButton.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Expire invoice form
|
||||
expireForm.submit(e => {
|
||||
e.preventDefault();
|
||||
|
||||
const form = $(e.target);
|
||||
const submitButton = form.find('button[type=submit]');
|
||||
const expireButton = form.find('[type=submit]');
|
||||
successAlert.hide();
|
||||
errorAlert.hide();
|
||||
|
||||
$.post({
|
||||
url: expireForm.attr('action'),
|
||||
success(data) {
|
||||
const { successMessage } = data;
|
||||
successAlert.html(successMessage).show();
|
||||
expireButton.hide();
|
||||
},
|
||||
complete() {
|
||||
submitButton.prop('disabled', false);
|
||||
},
|
||||
error(xhr) {
|
||||
const { errorMessage } = JSON.parse(xhr.responseText);
|
||||
errorAlert.html(errorMessage).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
Loading…
Add table
Reference in a new issue