update bring

This commit is contained in:
Kukks
2024-02-12 12:09:18 +01:00
parent ecfe5d116f
commit 537923a355
5 changed files with 211 additions and 117 deletions

View File

@@ -20,6 +20,30 @@ public class BringinClient
private HttpClient HttpClient { get; set; }
public static HttpClient CreateClient(IHttpClientFactory httpClientFactory, string? apiKey = null)
{
var httpClient = httpClientFactory.CreateClient("bringin");
httpClient.BaseAddress = new Uri("https://dev.bringin.xyz");
if(apiKey != null)
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("api-key", apiKey);
return httpClient;
}
public static async Task<Uri> OnboardUri(HttpClient httpClient, Uri callback)
{
var content = new StringContent(JsonConvert.SerializeObject(new
{
callback
}), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"/api/v0/application/btcpay/signup-url", content);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode) return new Uri(JObject.Parse(responseContent)["signupURL"].ToString());
return new Uri("https://dev-app.bringin.xyz");
var error = JObject.Parse(responseContent).ToObject<BringinErrorResponse>();
throw new BringinException(error);
}
public async Task<string> GetUserId()
{
var response = await HttpClient.GetAsync($"/api/v0/user/user-id");

View File

@@ -0,0 +1,96 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Plugins.Bringin;
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Route("plugins/{storeId}/Bringin")]
public class BringinController : Controller
{
private readonly BringinService _bringinService;
private readonly IHttpClientFactory _httpClientFactory;
public BringinController(BringinService bringinService, IHttpClientFactory httpClientFactory)
{
_bringinService = bringinService;
_httpClientFactory = httpClientFactory;
}
[HttpGet("onboard")]
public async Task<IActionResult> Onboard(string storeId)
{
var vm = await _bringinService.Update(storeId);
var callbackUri = Url.Action("Callback", "Bringin", new
{
code = vm.Code,
storeId
}, Request.Scheme);
var httpClient = BringinClient.CreateClient(_httpClientFactory);
var onboardUri = await BringinClient.OnboardUri(httpClient, new Uri(callbackUri));
return Redirect(onboardUri.ToString());
}
[HttpGet("")]
public async Task<IActionResult> Edit()
{
return View();
}
[HttpPost("callback")]
[HttpGet("callback")]
public async Task<IActionResult> Callback(string storeId, string code, [FromBody]BringinVerificationUpdate content)
{
var vm = await _bringinService.Update(storeId);
if(vm.Code != code) return BadRequest();
if(content.verificationStatus != "APPROVED") return BadRequest("Verification not approved");
if (string.IsNullOrEmpty(vm.ApiKey) && !string.IsNullOrEmpty(content.apikey))
{
vm.ApiKey = content.apikey;
await _bringinService.Update(storeId, vm);
}
return Ok();
}
public class BringinVerificationUpdate
{
public string userId { get; set; }
public string apikey { get; set; }
public string verificationStatus { get; set; }
}
// [HttpGet("callback")]
// public async Task<IActionResult> Callback(string storeId, string apiKey, string code)
// {
// //truncate with showing only first 3 letters on start ond end
//
// var truncatedApikey = apiKey.Substring(0, 3) + "***" + apiKey.Substring(apiKey.Length - 3);
//
// return View("Confirm",
// new ConfirmModel("Confirm Bringin API Key",
// $"You are about to set your Bringin API key to {truncatedApikey}", "Set", "btn-primary"));
// }
//
// [HttpPost("callback")]
// public async Task<IActionResult> CallbackConfirm(string storeId, string apiKey)
// {
// var vm = await _bringinService.Update(storeId);
// vm.ApiKey = apiKey;
// await _bringinService.Update(storeId, vm);
// return RedirectToAction("Edit", new {storeId});
// }
}

View File

@@ -1,12 +1,7 @@
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services;
using BTCPayServer.Client;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -27,44 +22,4 @@ public class BringinPlugin : BaseBTCPayServerPlugin
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Bringin/Nav",
"store-integrations-nav"));
}
}
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Route("plugins/{storeId}/Bringin")]
public class BringinController : Controller
{
private readonly BringinService _bringinService;
public BringinController(BringinService bringinService)
{
_bringinService = bringinService;
}
[HttpGet("")]
public async Task<IActionResult> Edit()
{
return View();
}
[HttpGet("callback")]
public async Task<IActionResult> Callback(string storeId, string apiKey)
{
//truncate with showing only first 3 letters on start ond end
var truncatedApikey = apiKey.Substring(0, 3) + "***" + apiKey.Substring(apiKey.Length - 3);
return View("Confirm",
new ConfirmModel("Confirm Bringin API Key",
$"You are about to set your Bringin API key to {truncatedApikey}", "Set", "btn-primary"));
}
[HttpPost("callback")]
public async Task<IActionResult> CallbackConfirm(string storeId, string apiKey)
{
var vm = await _bringinService.Update(storeId);
vm.ApiKey = apiKey;
await _bringinService.Update(storeId, vm);
return RedirectToAction("Edit", new {storeId});
}
}

View File

@@ -197,56 +197,34 @@ public class BringinService : EventHostedServiceBase
}
if (SupportedMethods.All(supportedMethod => supportedMethod.PaymentMethod != pmi))
try
{
//only LN is supported for now
continue;
}
var bringinClient = bringinStoreSetting.CreateClient(_httpClientFactory);
var supportedMethod = SupportedMethods.First(supportedMethod => supportedMethod.PaymentMethod == pmi);
var bringinClient = bringinStoreSetting.CreateClient(_httpClientFactory);
var host = await Dns.GetHostEntryAsync(Dns.GetHostName(), CancellationToken.None);
var ipToUse = host.AddressList
.FirstOrDefault(address => address.AddressFamily == AddressFamily.InterNetwork)?.ToString();
var thresholdAmount = methodSetting.Value.Threshold;
if (methodSetting.Value.FiatThreshold)
{
var rate = await bringinClient.GetRate();
thresholdAmount = methodSetting.Value.Threshold / rate.BringinPrice;
}
if (methodSetting.Value.CurrentBalance >= thresholdAmount)
{
var request = new BringinClient.CreateOrderRequest()
var thresholdAmount = methodSetting.Value.Threshold;
if (methodSetting.Value.FiatThreshold)
{
SourceAmount = Money.Coins(methodSetting.Value.CurrentBalance).Satoshi,
IP = ipToUse,
PaymentMethod = supportedMethod.bringinMethod
};
var order = await bringinClient.PlaceOrder(request);
var orderMoney = Money.Satoshis(order.Amount);
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(pmi.CryptoCode);
var claim = await _pullPaymentHostedService.Claim(new ClaimRequest()
var rate = await bringinClient.GetRate();
thresholdAmount = methodSetting.Value.Threshold / rate.BringinPrice;
}
if (methodSetting.Value.CurrentBalance >= thresholdAmount)
{
PaymentMethodId = pmi,
StoreId = storeId,
Destination = new BoltInvoiceClaimDestination(order.Invoice, BOLT11PaymentRequest.Parse(order.Invoice, network.NBitcoinNetwork)),
Value = orderMoney.ToUnit(MoneyUnit.BTC),
PreApprove = true,
Metadata = JObject.FromObject(new
var payoutId = await CreateOrder(storeId, pmi, Money.Coins(methodSetting.Value.CurrentBalance)
, true);
if (payoutId is not null)
{
Source = "Bringin"
})
});
if (claim.Result == ClaimRequest.ClaimResult.Ok)
{
methodSetting.Value.CurrentBalance -= orderMoney.ToUnit(MoneyUnit.BTC);
methodSetting.Value.PendingPayouts.Add(claim.PayoutData.Id);
result = true;
methodSetting.Value.CurrentBalance -= methodSetting.Value.CurrentBalance;
methodSetting.Value.PendingPayouts.Add(payoutId);
result = true;
}
}
}
catch (Exception e)
{
_logger.LogError(e, "Could not create payout");
}
}
if (result)
@@ -259,11 +237,11 @@ var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(pmi.CryptoCode);
return result;
}
public async Task<string> CreateOrder(string storeId, PaymentMethodId paymentMethodId, Money amountBtc, bool payout)
public async Task<string?> CreateOrder(string storeId, PaymentMethodId paymentMethodId, Money amountBtc, bool payout)
{
if (SupportedMethods.All(supportedMethod => supportedMethod.PaymentMethod != paymentMethodId))
{
throw new NotSupportedException("Only LN is supported for now");
throw new NotSupportedException($"{paymentMethodId.ToPrettyString()} Payment method not supported");
}
var settings = _settings[storeId];
@@ -397,7 +375,7 @@ var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(pmi.CryptoCode);
public static readonly SupportedMethodOptions[] SupportedMethods = new[]
{
new SupportedMethodOptions(new PaymentMethodId("BTC", LightningPaymentType.Instance), true, 100m, "LIGHTNING")
new SupportedMethodOptions(new PaymentMethodId("BTC", LightningPaymentType.Instance), true, 15, "LIGHTNING")
};
private ConcurrentDictionary<string, (IDisposable, BringinStoreSettings, DateTimeOffset Expiry)> _editModes = new();
@@ -472,6 +450,7 @@ var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(pmi.CryptoCode);
public const string BringinSettings = "BringinSettings";
public bool Enabled { get; set; } = true;
public string ApiKey { get; set; }
public string Code { get; set; } = Guid.NewGuid().ToString();
public Dictionary<string, PaymentMethodSettings> MethodSettings { get; set; } = new();
public class PaymentMethodSettings
@@ -485,11 +464,21 @@ var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(pmi.CryptoCode);
public BringinClient CreateClient(IHttpClientFactory httpClientFactory)
{
var backend = new Uri("https://dev.bringin.xyz");
var httpClient = httpClientFactory.CreateClient("bringin");
httpClient.BaseAddress = backend;
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("api-key", ApiKey);
var httpClient = BringinClient.CreateClient(httpClientFactory, ApiKey);
return new BringinClient(ApiKey, httpClient);
}
}
public void ResetBalance(string storeId, PaymentMethodId pmi)
{
_ = HandleStoreAction(storeId, async bringinStoreSettings =>
{
if (bringinStoreSettings.MethodSettings.TryGetValue(pmi.ToString(), out var methodSettings) && methodSettings.CurrentBalance > 0)
{
methodSettings.CurrentBalance = 0;
await _storeRepository.UpdateSetting(storeId, BringinStoreSettings.BringinSettings, bringinStoreSettings);
_settings.AddOrReplace(storeId, bringinStoreSettings);
}
});
}
}

View File

@@ -71,9 +71,10 @@
{
if (firstRender)
{
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});
_settings = BringinService.IsInEditMode(StoreId)? await BringinService.Update(StoreId): await BringinService.Get(StoreId);
_settings = BringinService.IsInEditMode(StoreId) ? await BringinService.Update(StoreId) : await BringinService.Get(StoreId);
_pms = (await StoreRepository.FindStore(StoreId)).GetSupportedPaymentMethods(BTCPayNetworkProvider).Select(method => method.PaymentId).Where(id => id.CryptoCode == "BTC").ToArray();
_pps = (await PayoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery()
{
@@ -150,17 +151,23 @@
{
if (_saving)
return;
_saving = true;
await TestApiKey();
if (ApiKeyError)
return;
SaveError = null;
if (!EditMode)
return;
await BringinService.Update(StoreId, _settings);
EditMode = false;
_saving = false;
fetcherCTS?.Cancel();
try
{
_saving = true;
await TestApiKey();
if (ApiKeyError)
return;
SaveError = null;
if (!EditMode)
return;
await BringinService.Update(StoreId, _settings);
EditMode = false;
fetcherCTS?.Cancel();
}
finally
{
_saving = false;
}
}
CancellationTokenSource fetcherCTS;
@@ -250,7 +257,7 @@
public async Task SubmitOrder()
{
_saving = true;
await InvokeAsync(StateHasChanged);
var pm = PaymentMethodId.TryParse(ManualOrderPaymentMethod);
if (pm is null)
@@ -269,6 +276,7 @@
SaveError = e.Message;
_saving = false;
}
await InvokeAsync(StateHasChanged);
}
@@ -281,10 +289,18 @@
// private bool ManualOrderPayout = true;
public string PmiLink;
public string OnboardLink;
private void ResetBalance(PaymentMethodId pmi)
{
BringinService.ResetBalance(StoreId, pmi);
}
}
<div class="widget store-numbers" id="Bringin-Info" style="grid-column-start: 1; grid-column-end: 9;">
<div class="widget store-wallet-balance" id="Bringin-Info">
@if (!IsLoaded)
{
<h2 class="text-muted">Loading Bringin offramp</h2>
@@ -354,6 +370,10 @@
<div class="row">
<div class="col-xxl-constrain">
<p class="text-secondary my-3">
Create an order irrespective of the current balance tracked by the plugin.
</p>
@if (!string.IsNullOrEmpty(SaveError))
{
<div class="alert alert-danger">@SaveError</div>
@@ -362,7 +382,7 @@
@if (!string.IsNullOrEmpty(ManualOrderResult))
{
<div class="alert alert-success">Payout created: @ManualOrderResult</div>
<div class="form-group">
<button class="btn btn-primary" @onclick="CancelManual">Go back</button>
</div>
@@ -455,8 +475,15 @@
<h3 class="d-inline-block me-1" data-balance="@method.Value.CurrentBalance" data-sensitive>@DisplayFormatter.Currency(method.Value.CurrentBalance, "BTC", DisplayFormatter.CurrencyFormat.None)</h3>
<span class="text-secondary fw-semibold currency">BTC</span>
<span class="text-secondary"> (@DisplayFormatter.Currency(balanceInFiat.Value, "EUR", DisplayFormatter.CurrencyFormat.Code)) pending to forward once @DisplayFormatter.Currency(thresholdinBtc.Value, "BTC", DisplayFormatter.CurrencyFormat.Code) (@DisplayFormatter.Currency(method.Value.Threshold, "EUR")) is reached.</span>
</div>
@if (method.Value.CurrentBalance > 0)
{
<button class="btn btn-link" @onclick="() => ResetBalance(pmi)">Reset balance</button>
//Clear balance
}
</div>
}
@if (method.Value.PendingPayouts.Any())
{
<div class="balance d-flex align-items-baseline gap-1">
@@ -466,7 +493,7 @@
}
@if (!_pps.Contains(pmi) && PmiLink is not null)
{
<p class="text-warning">@((MarkupString)PmiLink)</p>
<p class="text-warning">@((MarkupString) PmiLink)</p>
}
</div>
}
@@ -485,9 +512,12 @@
<div class="form-group">
<label class="form-label">API Key</label>
<input type="password" class="form-control" @oninput="() => InvokeAsync(StateHasChanged)" @bind="ApiKey"/>
<p class="my-2">You can get one <a href="https://dev-app.bringin.xyz/" target="_blank">here</a></p>
<input type="password" class="form-control" @bind:event="oninput" @bind="ApiKey"/>
<p class="my-2">You can get one <a href="@OnboardLink" target="_blank">here</a></p>
@if (ApiKeyError)
{
<div class="text-danger">Invalid API Key</div>
}
</div>
</div>
</div>
@@ -500,7 +530,7 @@
<div class="form-group">
<label class="form-label">API Key</label>
<input type="password" class="form-control" @bind="_settings.ApiKey"/>
<p class="my-2">You can get one <a href="https://dev-app.bringin.xyz/" target="_blank">here</a></p>
<p class="my-2">You can get one <a href="@OnboardLink" target="_blank">here</a></p>
@if (ApiKeyError)
{
<div class="text-danger">Invalid API Key</div>