mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
bringin poc
This commit is contained in:
@@ -7,8 +7,8 @@
|
||||
<!-- -->
|
||||
<!-- Plugin specific properties -->
|
||||
<PropertyGroup>
|
||||
<Product>Dynamic Reports</Product>
|
||||
<Description>Allows you to create custom reports using SQL.</Description>
|
||||
<Product>Bringin</Product>
|
||||
<Description>Euro Offramp</Description>
|
||||
<Version>1.0.0</Version>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
@@ -33,4 +33,9 @@
|
||||
<EmbeddedResource Include="Resources\**" />
|
||||
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Views\Bringin\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -43,40 +43,57 @@ public class BringinClient
|
||||
public async Task<CreateOrderResponse> PlaceOrder(CreateOrderRequest request)
|
||||
{
|
||||
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
|
||||
var response = await HttpClient.PostAsync($"/api/v0/offramp/order/lightning", content);
|
||||
var response = await HttpClient.PostAsync($"/api/v0/offramp/order", content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
if (response.IsSuccessStatusCode) return JObject.Parse(responseContent).ToObject<CreateOrderResponse>();
|
||||
var error = JObject.Parse(responseContent).ToObject<BringinErrorResponse>();
|
||||
throw new BringinException(error);
|
||||
}
|
||||
|
||||
public async Task<RateResponse> GetOrderInfo(GetOrderRequest request)
|
||||
public async Task<decimal> GetFiatBalance(string currency = "EUR")
|
||||
{
|
||||
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
|
||||
var response = await HttpClient.PostAsync($"/api/v0/offramp/order/lightning", content);
|
||||
var content = new StringContent(JsonConvert.SerializeObject(new
|
||||
{
|
||||
currency
|
||||
}), Encoding.UTF8, "application/json");
|
||||
var response = await HttpClient.PostAsync($"/api/v0/user/get-balance/fiat", content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
if (response.IsSuccessStatusCode) return JObject.Parse(responseContent).ToObject<RateResponse>();
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var balance = JObject.Parse(responseContent).ToObject<BalanceResponse>();
|
||||
return balance.Balance / 100m; //response is in cents
|
||||
}
|
||||
var error = JObject.Parse(responseContent).ToObject<BringinErrorResponse>();
|
||||
throw new BringinException(error);
|
||||
}
|
||||
|
||||
public class GetOrderResponse
|
||||
public class BalanceResponse
|
||||
{
|
||||
public string OrderId { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string SubType { get; set; }
|
||||
|
||||
[JsonProperty("balance")]
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal SourceAmount { get; set; }
|
||||
|
||||
public string SourceCurrency { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal DestinationAmount { get; set; }
|
||||
|
||||
public string DestinationCurrency { get; set; }
|
||||
public BringinPrice BringinPrice { get; set; }
|
||||
public decimal Balance {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
//
|
||||
// public class GetOrderResponse
|
||||
// {
|
||||
// public string OrderId { get; set; }
|
||||
// public string Status { get; set; }
|
||||
// public string SubType { get; set; }
|
||||
//
|
||||
// [JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
// public decimal SourceAmount { get; set; }
|
||||
//
|
||||
// public string SourceCurrency { get; set; }
|
||||
//
|
||||
// [JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
// public decimal DestinationAmount { get; set; }
|
||||
//
|
||||
// public string DestinationCurrency { get; set; }
|
||||
// public BringinPrice BringinPrice { get; set; }
|
||||
// }
|
||||
|
||||
public class BringinPrice
|
||||
{
|
||||
@@ -86,32 +103,31 @@ public class BringinClient
|
||||
public string Currency { get; set; }
|
||||
}
|
||||
|
||||
public class GetOrderRequest
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
public string OrderId { get; set; }
|
||||
}
|
||||
|
||||
public class CreateOrderResponse
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty("amount")]
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
[JsonProperty("invoice")]
|
||||
public string Invoice { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
[JsonProperty("expiresAt")]
|
||||
public string Expiry { get; set; }
|
||||
public long Expiry { get; set; }
|
||||
}
|
||||
|
||||
public class CreateOrderRequest
|
||||
{
|
||||
{ [JsonProperty("sourceAmount")]
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal SourceAmount { get; set; }
|
||||
|
||||
[JsonProperty("ipAddress")] public string IP { get; set; }
|
||||
[JsonProperty("paymentMethod")]
|
||||
public string PaymentMethod { get; set; }
|
||||
}
|
||||
|
||||
public class RateResponse
|
||||
@@ -119,8 +135,8 @@ public class BringinClient
|
||||
public string Ticker { get; set; }
|
||||
public string Currency { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public string Timestamp { get; set; }
|
||||
|
||||
public long Timestamp { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Price { get; set; }
|
||||
@@ -136,14 +152,4 @@ public class BringinClient
|
||||
public string ErrorCode { get; set; }
|
||||
public JObject ErrorDetails { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class BringinException : Exception
|
||||
{
|
||||
private readonly BringinClient.BringinErrorResponse _error;
|
||||
|
||||
public BringinException(BringinClient.BringinErrorResponse error)
|
||||
{
|
||||
_error = error;
|
||||
}
|
||||
}
|
||||
176
Plugins/BTCPayServer.Plugins.Bringin/BringinCustodian.cs
Normal file
176
Plugins/BTCPayServer.Plugins.Bringin/BringinCustodian.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Custodians;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Forms;
|
||||
using BTCPayServer.Payments;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Plugins.Bringin;
|
||||
|
||||
public class BringinApiKeyFormComponentProvider:FormComponentProviderBase
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
public override string View => "Bringin/ApiKeyElement";
|
||||
|
||||
public BringinApiKeyFormComponentProvider(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public override void Validate(Form form, Field field)
|
||||
{
|
||||
if (field.Required)
|
||||
{
|
||||
ValidateField<RequiredAttribute>(field);
|
||||
}
|
||||
if(field.ValidationErrors.Any())
|
||||
return;
|
||||
var httpClient = _httpClientFactory.CreateClient("bringin");
|
||||
httpClient.BaseAddress = new Uri("https://dev.bringin.xyz");
|
||||
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("api-key", GetValue(form, field));
|
||||
try
|
||||
{
|
||||
var userId = new BringinClient(GetValue(form, field), httpClient).GetUserId().GetAwaiter().GetResult();
|
||||
if(userId is null)
|
||||
field.ValidationErrors.Add("Invalid API Key");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
field.ValidationErrors.Add("Invalid API Key");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
||||
{
|
||||
typeToComponentProvider.Add("bringin-apikey", this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class BringinCustodian : ICustodian, ICanDeposit
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly FormDataService _formDataService;
|
||||
public string Code => "bringin";
|
||||
|
||||
public string Name => "Bringin";
|
||||
|
||||
public BringinCustodian(IHttpClientFactory httpClientFactory, FormDataService formDataService)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_formDataService = formDataService;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, decimal>> GetAssetBalancesAsync(JObject config,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var bringinClient = FromConfig(config);
|
||||
if (bringinClient is null)
|
||||
throw new BadConfigException(Array.Empty<string>());
|
||||
|
||||
var balance = await bringinClient.GetFiatBalance();
|
||||
return new Dictionary<string, decimal>()
|
||||
{
|
||||
{"EUR", balance}
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<Form> GetConfigForm(JObject config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
var f = new Form();
|
||||
|
||||
var fieldset = Field.CreateFieldset();
|
||||
fieldset.Label = "Connection details";
|
||||
fieldset.Fields.Add(new Field()
|
||||
{
|
||||
Name = "apiKey",
|
||||
Label = "API Key",
|
||||
Type = "bringin-apikey",
|
||||
Value = config["apiKey"]?.Value<string>(),
|
||||
Required = true,
|
||||
HelpText = "Enter your Bringin API Key which can be obtained from <a href=\"https://dev-app.bringin.xyz\" target=\"_blank\">here</a>"
|
||||
});
|
||||
// fieldset.Fields.Add(new Field()
|
||||
// {
|
||||
// Name = "server",
|
||||
// Label = "Bringin Server (optional)",
|
||||
// Type = "password",
|
||||
// Value = config["server"]?.Value<string>(),
|
||||
// Required = false,
|
||||
// OriginalValue = "https://dev.bringin.xyz",
|
||||
// HelpText = "Enter the Bringin server URL. This is optional and defaults to https://dev.bringin.xyz"
|
||||
// });
|
||||
//
|
||||
f.Fields.Add(fieldset);
|
||||
return f;
|
||||
}
|
||||
|
||||
public async Task<DepositAddressData> GetDepositAddressAsync(string paymentMethod, JObject config,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var amount = config["depositAddressConfig"]?.Value<decimal>();
|
||||
|
||||
var bringinClient = FromConfig(config);
|
||||
if (bringinClient is null)
|
||||
throw new Exception("Invalid API Key");
|
||||
if (amount is null or <= 0)
|
||||
{
|
||||
var rate = await bringinClient.GetRate();
|
||||
//rate.bringinRate is the price for 1 BTC in EUR
|
||||
//100eur = 1/rate.bringinRate BTC
|
||||
amount = 100m / rate.BringinPrice;
|
||||
}
|
||||
// var rate = await bringinClient.GetRate();
|
||||
var host = await Dns.GetHostEntryAsync(Dns.GetHostName(), cancellationToken);
|
||||
var ipToUse = host.AddressList.FirstOrDefault(address => address.AddressFamily == AddressFamily.InterNetwork)?.ToString();
|
||||
var request = new BringinClient.CreateOrderRequest()
|
||||
{
|
||||
SourceAmount = Money.Coins(amount.Value).Satoshi,
|
||||
IP = ipToUse,
|
||||
PaymentMethod = "LIGHTNING"
|
||||
};
|
||||
var order = await bringinClient.PlaceOrder(request);
|
||||
return new DepositAddressData()
|
||||
{
|
||||
Address = order.Invoice
|
||||
};
|
||||
}
|
||||
|
||||
public string[] GetDepositablePaymentMethods()
|
||||
{
|
||||
return new[] {new PaymentMethodId("BTC", LightningPaymentType.Instance).ToString()};
|
||||
}
|
||||
|
||||
private BringinClient? FromConfig(JObject config)
|
||||
{
|
||||
Uri backend = new Uri("https://dev.bringin.xyz");
|
||||
if (config.TryGetValue("apiKey", out var apiKey))
|
||||
{
|
||||
// if (config.TryGetValue("server", out var serverToken) && serverToken.Value<string>() is { } server &&
|
||||
// !string.IsNullOrEmpty(server))
|
||||
// {
|
||||
// backend = new Uri(server);
|
||||
// }
|
||||
|
||||
var httpClient = _httpClientFactory.CreateClient("bringin");
|
||||
httpClient.BaseAddress = backend;
|
||||
|
||||
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("api-key", apiKey.Value<string>());
|
||||
return new BringinClient(apiKey.Value<string>(), httpClient);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
13
Plugins/BTCPayServer.Plugins.Bringin/BringinException.cs
Normal file
13
Plugins/BTCPayServer.Plugins.Bringin/BringinException.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Plugins.Bringin;
|
||||
|
||||
public class BringinException : Exception
|
||||
{
|
||||
private readonly BringinClient.BringinErrorResponse _error;
|
||||
|
||||
public BringinException(BringinClient.BringinErrorResponse error)
|
||||
{
|
||||
_error = error;
|
||||
}
|
||||
}
|
||||
26
Plugins/BTCPayServer.Plugins.Bringin/BringinPlugin.cs
Normal file
26
Plugins/BTCPayServer.Plugins.Bringin/BringinPlugin.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Custodians;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Forms;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Plugins.Bringin;
|
||||
|
||||
public class BringinPlugin : BaseBTCPayServerPlugin
|
||||
{
|
||||
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
|
||||
{
|
||||
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"}
|
||||
};
|
||||
|
||||
public override void Execute(IServiceCollection applicationBuilder)
|
||||
{
|
||||
applicationBuilder.AddStartupTask<CustodianEnablerTask>();
|
||||
applicationBuilder.AddSingleton<ICustodian, BringinCustodian>();
|
||||
applicationBuilder.AddSingleton<IFormComponentProvider, BringinApiKeyFormComponentProvider>();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
24
Plugins/BTCPayServer.Plugins.Bringin/CustodianEnablerTask.cs
Normal file
24
Plugins/BTCPayServer.Plugins.Bringin/CustodianEnablerTask.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Services;
|
||||
|
||||
namespace BTCPayServer.Plugins.Bringin;
|
||||
|
||||
public class CustodianEnablerTask: IStartupTask
|
||||
{
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
|
||||
public CustodianEnablerTask(SettingsRepository settingsRepository)
|
||||
{
|
||||
_settingsRepository = settingsRepository;
|
||||
}
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var policySettings = await _settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
if(policySettings.Experimental)
|
||||
return;
|
||||
policySettings.Experimental = true;
|
||||
await _settingsRepository.UpdateSetting(policySettings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
@model BTCPayServer.Abstractions.Form.Field
|
||||
@{
|
||||
var isInvalid = ViewContext.ModelState[Model.Name]?.ValidationState is Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid;
|
||||
var errors = isInvalid ? ViewContext.ModelState[Model.Name].Errors : null;
|
||||
}
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="@Model.Name"@(Model.Required ? " data-required" : "")>
|
||||
@Safe.Raw(Model.Label)
|
||||
</label>
|
||||
<input id="@Model.Name" type="password" class="form-control @(errors is null ? "" : "is-invalid")"
|
||||
name="@Model.Name" value="@Model.Value" data-val="true" readonly="@Model.Constant"
|
||||
@if (!string.IsNullOrEmpty(Model.HelpText))
|
||||
{
|
||||
@Safe.Raw($" aria-describedby=\"HelpText-{Model.Name}\"")
|
||||
}
|
||||
@if (Model.Required)
|
||||
{
|
||||
@Safe.Raw($" data-val-required=\"{Model.Label} is required.\" required")
|
||||
}
|
||||
/>
|
||||
<span class="text-danger" data-valmsg-for="@Model.Name" data-valmsg-replace="true">@(isInvalid && errors.Any() ? errors.First().ErrorMessage : string.Empty)</span>
|
||||
@if (!string.IsNullOrEmpty(Model.HelpText))
|
||||
{
|
||||
<div id="@($"HelpText-{Model.Name}")" class="form-text">
|
||||
@Safe.Raw(Model.HelpText)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
5
Plugins/BTCPayServer.Plugins.Bringin/_ViewImports.cshtml
Normal file
5
Plugins/BTCPayServer.Plugins.Bringin/_ViewImports.cshtml
Normal file
@@ -0,0 +1,5 @@
|
||||
@using BTCPayServer.Abstractions.Services
|
||||
@inject Safe Safe
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, BTCPayServer
|
||||
@addTagHelper *, BTCPayServer.Abstractions
|
||||
Reference in New Issue
Block a user