mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
update bring
This commit is contained in:
@@ -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");
|
||||
|
||||
96
Plugins/BTCPayServer.Plugins.Bringin/BringinController.cs
Normal file
96
Plugins/BTCPayServer.Plugins.Bringin/BringinController.cs
Normal 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});
|
||||
// }
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -28,43 +23,3 @@ public class BringinPlugin : BaseBTCPayServerPlugin
|
||||
"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});
|
||||
}
|
||||
}
|
||||
@@ -197,19 +197,10 @@ public class BringinService : EventHostedServiceBase
|
||||
}
|
||||
|
||||
|
||||
if (SupportedMethods.All(supportedMethod => supportedMethod.PaymentMethod != pmi))
|
||||
try
|
||||
{
|
||||
//only LN is supported for now
|
||||
continue;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -219,35 +210,22 @@ public class BringinService : EventHostedServiceBase
|
||||
|
||||
if (methodSetting.Value.CurrentBalance >= thresholdAmount)
|
||||
{
|
||||
var request = new BringinClient.CreateOrderRequest()
|
||||
var payoutId = await CreateOrder(storeId, pmi, Money.Coins(methodSetting.Value.CurrentBalance)
|
||||
, true);
|
||||
if (payoutId is not null)
|
||||
{
|
||||
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()
|
||||
{
|
||||
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
|
||||
{
|
||||
Source = "Bringin"
|
||||
})
|
||||
});
|
||||
if (claim.Result == ClaimRequest.ClaimResult.Ok)
|
||||
{
|
||||
methodSetting.Value.CurrentBalance -= orderMoney.ToUnit(MoneyUnit.BTC);
|
||||
methodSetting.Value.PendingPayouts.Add(claim.PayoutData.Id);
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,6 +151,8 @@
|
||||
{
|
||||
if (_saving)
|
||||
return;
|
||||
try
|
||||
{
|
||||
_saving = true;
|
||||
await TestApiKey();
|
||||
if (ApiKeyError)
|
||||
@@ -159,9 +162,13 @@
|
||||
return;
|
||||
await BringinService.Update(StoreId, _settings);
|
||||
EditMode = false;
|
||||
_saving = false;
|
||||
fetcherCTS?.Cancel();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
CancellationTokenSource fetcherCTS;
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@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>
|
||||
|
||||
Reference in New Issue
Block a user