update bringin

This commit is contained in:
Kukks
2024-03-21 14:55:20 +01:00
parent 66b735da6c
commit 1c64038245
4 changed files with 178 additions and 49 deletions

View File

@@ -117,6 +117,71 @@ public class BringinClient
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Balance { get; set; }
}
public async Task<GetTransactionListResponse> GetTransactions()
{
var content = new StringContent(JsonConvert.SerializeObject(new
{
startDate = DateTimeOffset.UtcNow.AddDays(-30).ToUnixTimeMilliseconds(),
endDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
}), Encoding.UTF8, "application/json");
var response = await HttpClient.PostAsync($"/api/v0/account/transactions", content);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
return JObject.Parse(responseContent).ToObject<GetTransactionListResponse>();
}
var error = JObject.Parse(responseContent).ToObject<BringinErrorResponse>();
throw new BringinException(error);
}
public class GetTransactionListResponse
{
[JsonProperty("transactions")]
public BringinTransaction[] Transactions { get; set; }
}
public class BringinTransaction
{
// {
// "orderId": "3521154c-30b4-480c-834d-38f80d507963",
// "type": "OFFRAMP_WITHOUT_FIAT_WITHDRAWAL",
// "subType": "LIGHTNING",
// "sourceAmount": "100000",
// "sourceCurrency": "BTC",
// "destinationAmount": "3816",
// "destinationAddress": "b0a4c862-c941-4d3c-8727-18e5097a3b5a",
// "destinationCurrency": "EUR",
// "status": "SUCCESSFUL",
// "createdAt": "2024-01-18T14:02:59.709Z",
// }
[JsonProperty("orderId")]
public string OrderId { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("subType")]
public string SubType { get; set; }
[JsonProperty("sourceAmount")]
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal SourceAmount { get; set; }
[JsonProperty("sourceCurrency")]
public string SourceCurrency { get; set; }
[JsonProperty("destinationAmount")]
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal DestinationAmount { get; set; }
[JsonProperty("destinationCurrency")]
public string DestinationCurrency { get; set; }
[JsonProperty("destinationAddress")]
public string DestinationAddress { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("createdAt")]
public DateTimeOffset CreatedAt { get; set; }
}
//
// public class GetOrderResponse
// {
@@ -154,6 +219,7 @@ public class BringinClient
public decimal Amount { get; set; }
[JsonProperty("invoice")] public string Invoice { get; set; }
[JsonProperty("depositAddress")] public string DepositAddress { get; set; }
[JsonProperty("expiresAt")] public long Expiry { get; set; }
}
@@ -188,6 +254,7 @@ public class BringinClient
public string Message { get; set; }
public string StatusCode { get; set; }
public string ErrorCode { get; set; }
public JObject ErrorDetails { get; set; }
public string ErrorMessage { get; set; }
public JToken ErrorDetails { get; set; }
}
}

View File

@@ -6,7 +6,7 @@ public class BringinException : Exception
{
private readonly BringinClient.BringinErrorResponse _error;
public BringinException(BringinClient.BringinErrorResponse error)
public BringinException(BringinClient.BringinErrorResponse error):base(error.Message?? error.ErrorMessage)
{
_error = error;
}

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -27,6 +28,15 @@ using PayoutData = BTCPayServer.Data.PayoutData;
namespace BTCPayServer.Plugins.Bringin;
public static class StringExtensions
{
public static string ToHumanReadable(this string str)
{
return string.Join(' ', str.Split('_', '-').Select(part =>
CultureInfo.CurrentCulture.TextInfo.ToTitleCase(part.ToLower(CultureInfo.CurrentCulture))));
}
}
public class BringinService : EventHostedServiceBase
{
private readonly ILogger<BringinService> _logger;
@@ -265,7 +275,7 @@ public class BringinService : EventHostedServiceBase
var rate = await bringinClient.GetRate();
var thresholdAmount = supportedMethod.FiatMinimumAmount / rate.BringinPrice;
if (amountBtc.ToDecimal(MoneyUnit.BTC) < thresholdAmount)
if (amountBtc.ToDecimal(MoneyUnit.BTC) <= thresholdAmount)
{
throw new Exception($"Amount is too low. Minimum amount is {Money.Coins(thresholdAmount)} BTC");
}
@@ -283,15 +293,17 @@ public class BringinService : EventHostedServiceBase
if (!payout)
{
return order.Invoice;
return order.Invoice?? order.DepositAddress;
}
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
var destination = !string.IsNullOrEmpty(order.Invoice)? (IClaimDestination) new BoltInvoiceClaimDestination(order.Invoice, BOLT11PaymentRequest.Parse(order.Invoice, network.NBitcoinNetwork)):
new AddressClaimDestination(BitcoinAddress.Create(order.DepositAddress, network.NBitcoinNetwork));
var claim = await _pullPaymentHostedService.Claim(new ClaimRequest()
{
PaymentMethodId = paymentMethodId,
StoreId = storeId,
Destination = new BoltInvoiceClaimDestination(order.Invoice, BOLT11PaymentRequest.Parse(order.Invoice, network.NBitcoinNetwork)),
Destination = destination,
Value = orderMoney.ToUnit(MoneyUnit.BTC),
PreApprove = true,
Metadata = JObject.FromObject(new
@@ -379,7 +391,8 @@ public class BringinService : EventHostedServiceBase
public static readonly SupportedMethodOptions[] SupportedMethods = new[]
{
new SupportedMethodOptions(new PaymentMethodId("BTC", LightningPaymentType.Instance), true, 15, "LIGHTNING")
new SupportedMethodOptions(new PaymentMethodId("BTC", LightningPaymentType.Instance), true, 15, "LIGHTNING"),
new SupportedMethodOptions(new PaymentMethodId("BTC", BitcoinPaymentType.Instance), true, 20, "ON_CHAIN"),
};
private ConcurrentDictionary<string, (IDisposable, BringinStoreSettings, DateTimeOffset Expiry)> _editModes = new();

View File

@@ -11,7 +11,6 @@
@using NBitcoin
@implements IAsyncDisposable;
@code {
private BringinService.BringinStoreSettings? _settings;
private bool _isLoaded = false;
@@ -75,7 +74,7 @@
{
if (firstRender)
{
_readOnly = !(await AuthorizationService.AuthorizeAsync(HttpContextAccessor.HttpContext.User, StoreId, Policies.CanModifyStoreSettings )).Succeeded;
_readOnly = !(await AuthorizationService.AuthorizeAsync(HttpContextAccessor.HttpContext.User, StoreId, Policies.CanModifyStoreSettings)).Succeeded;
OnboardLink = LinkGenerator.GetUriByAction(HttpContextAccessor.HttpContext, "Onboard", "Bringin", new {StoreId});
PmiLink = $"A payout processor has not been configured for this payment method. Payouts generated by Bringin will not be automatically handled. <a href=\"{LinkGenerator.GetUriByAction(HttpContextAccessor.HttpContext, "ConfigureStorePayoutProcessors", "UIPayoutProcessors", new {StoreId})}\">Configure now</a>";
_callbackLink = LinkGenerator.GetUriByAction(HttpContextAccessor.HttpContext, "Callback", "Bringin", new {StoreId});
@@ -194,6 +193,7 @@
LastFiatBalance = await client.GetFiatBalance();
LastFiatRate = (await client.GetRate()).BringinPrice;
LastDataFetch = DateTimeOffset.UtcNow;
LastTxs = await client.GetTransactions();
_ = InvokeAsync(StateHasChanged);
}
finally
@@ -210,6 +210,8 @@
}
}
public BringinClient.GetTransactionListResponse LastTxs { get; set; }
private void UpdateDestinationValue(BringinService.BringinStoreSettings.PaymentMethodSettings settings, object eValue)
{
@@ -417,9 +419,9 @@
<div class="form-group">
@* <label class="form-label">Payment method</label> *@
<select @bind="ManualOrderPaymentMethod" class="form-select">
<option value="">Select a payment method</option>
@foreach (var opt in items)
{
<option value="">Select a payment method</option>
<option value="@opt.ToString()">@opt.ToPrettyString()</option>
}
</select>
@@ -569,49 +571,96 @@
</div>
</div>
<div class="row">
@foreach (var method in _settings.MethodSettings)
{
var pmId = PaymentMethodId.TryParse(method.Key);
if (pmId is null)
continue;
var supportedMethod = BringinService.SupportedMethods.FirstOrDefault(s => s.PaymentMethod.ToString() == method.Key);
<div class="card col-xxl-constrain col-12 @(_settings.MethodSettings.Count > 1 ? "col-xl-6" : "")">
<h5 class="card-header border-bottom-0 text-muted">@pmId.ToPrettyString()</h5>
<div class="card-body">
<div class="form-group">
<label class="form-label">Percentage</label>
<input type="range" value="@method.Value.PercentageToForward" @oninput="@((e) => UpdateDestinationValue(method.Value, e.Value))" min="0" step='0.01' class="form-range" max="100"/>
<div class="input-group input-group-sm">
<input type="number" step='0.01' value="@method.Value.PercentageToForward" @onchange="@((e) => UpdateDestinationValue(method.Value, e.Value))" class="form-control form-control-sm"/>
<span class="input-group-text">%</span>
</div>
<p class="text-muted my-2">Every time an invoice becomes Settled, we take the sum of all settled payments of this payment method, get the specified percentage of it and add it to the current balance.</p>
</div>
<div class="form-group">
<label class="form-label">Threshold</label>
<div class="input-group input-group-sm">
<input type="number" @bind="method.Value.Threshold" min="@supportedMethod?.FiatMinimumAmount" class="form-control form-control-sm"/>
<span class="input-group-text">@(method.Value.FiatThreshold ? "EUR" : "BTC")</span>
</div>
<p class="text-muted my-2">Once the threshold is reached, we create a payout sending the balance to Bringin to be converted.</p>
</div>
@if (supportedMethod?.FiatMinimum is not true)
{
<div class="container">
<div class="row gx-5">
@for (int i = 0; i < _settings.MethodSettings.Count; i++)
{
var method = _settings.MethodSettings.ElementAt(i);
var pmId = PaymentMethodId.TryParse(method.Key);
if (pmId is null)
continue;
var supportedMethod = BringinService.SupportedMethods.FirstOrDefault(s => s.PaymentMethod.ToString() == method.Key);
<div class="col-xxl-constrain col-12 @(_settings.MethodSettings.Count > 1 ? $"col-xl-6 {(i == 0 ? "border-end" : "")}" : "")">
<h5 class=" border-bottom-0 text-muted mb-4">@pmId.ToPrettyString()</h5>
<div class="card-body">
<div class="form-group">
<div class="d-flex align-items-center">
<input type="checkbox" class="btcpay-toggle me-2" @bind="method.Value.FiatThreshold"/>
<label class="form-label mb-0 me-1">Threshold in EUR</label>
<label class="form-label">Percentage</label>
<input type="range" value="@method.Value.PercentageToForward" @oninput="@((e) => UpdateDestinationValue(method.Value, e.Value))" min="0" step='0.01' class="form-range" max="100"/>
<div class="input-group input-group-sm">
<input type="number" step='0.01' value="@method.Value.PercentageToForward" @onchange="@((e) => UpdateDestinationValue(method.Value, e.Value))" class="form-control form-control-sm"/>
<span class="input-group-text">%</span>
</div>
<p class="text-muted my-2">Every time an invoice becomes Settled, we take the sum of all settled payments of this payment method, get the specified percentage of it and add it to the current balance.</p>
</div>
}
<div class="form-group">
<label class="form-label">Threshold</label>
<div class="input-group input-group-sm">
<input type="number" @bind="method.Value.Threshold" min="@supportedMethod?.FiatMinimumAmount" class="form-control form-control-sm"/>
<span class="input-group-text">@(method.Value.FiatThreshold ? "EUR" : "BTC")</span>
</div>
<p class="text-muted my-2">Once the threshold is reached, we create a payout sending the balance to Bringin to be converted.</p>
</div>
@if (supportedMethod?.FiatMinimum is not true)
{
<div class="form-group">
<div class="d-flex align-items-center">
<input type="checkbox" class="btcpay-toggle me-2" @bind="method.Value.FiatThreshold"/>
<label class="form-label mb-0 me-1">Threshold in EUR</label>
</div>
</div>
}
</div>
</div>
</div>
}
}
</div>
</div>
}
}
}
</div>
@if (LastTxs is not null)
{
<div class="widget store-numbers">
<header>
<h4 class="text-muted">Bringin Transactions</h4>
</header>
@if (LastTxs.Transactions.Any())
{
<div class="table-responsive my-0 " style=" max-height: 400px;">
<table class="table table-hover mt-3 mb-0">
<thead>
<tr>
<th>Date</th>
<th>Type</th>
<th>Status</th>
<th class="text-end">Amount</th>
</tr>
</thead>
<tbody>
@foreach (var tx in LastTxs.Transactions.OrderByDescending(transaction => transaction.CreatedAt))
{
<tr>
<td>@tx.CreatedAt.ToTimeAgo()</td>
<td>@tx.SubType.ToHumanReadable()</td>
<td>
@tx.Status.ToHumanReadable()
</td>
<td class="amount-col">
<span data-sensitive>@(tx.SourceCurrency == "BTC" ? Money.Satoshis(tx.SourceAmount).ToDecimal(MoneyUnit.BTC): tx.SourceAmount)@tx.SourceCurrency -> @tx.DestinationAmount @tx.DestinationCurrency </span>
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<p class="text-secondary mt-3 mb-0">
There are no recent transactions.
</p>
}
</div>
}