feat(infra): implement plaid service
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Going.Plaid" Version="6.56.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace FiscalOS.Infra.Accounts.Plaid;
|
||||
|
||||
public sealed record PlaidClientOptions
|
||||
{
|
||||
public string ClientId { get; init; } = string.Empty;
|
||||
public string Secret { get; init; } = string.Empty;
|
||||
|
||||
public IOptions<PlaidOptions> ToPlaidOptions()
|
||||
{
|
||||
return Options.Create<PlaidOptions>(new()
|
||||
{
|
||||
ClientId = ClientId,
|
||||
Secret = Secret
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record PlaidClientOptionsSetup : IConfigureOptions<PlaidClientOptions>
|
||||
{
|
||||
private const string SectionName = nameof(PlaidClientOptions);
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public PlaidClientOptionsSetup(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public void Configure(PlaidClientOptions options)
|
||||
{
|
||||
_configuration.GetSection(SectionName).Bind(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace FiscalOS.Infra.Accounts.Plaid;
|
||||
|
||||
public class PlaidException : Exception
|
||||
{
|
||||
public PlaidException()
|
||||
{
|
||||
}
|
||||
|
||||
public PlaidException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public PlaidException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,15 @@ public sealed class PlaidMetadata : InstitutionMetadata
|
||||
}
|
||||
|
||||
public static PlaidMetadata From(
|
||||
string plaidId,
|
||||
string plaidName,
|
||||
string? plaidId,
|
||||
string? plaidName,
|
||||
string encryptedAccessToken
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(plaidId, nameof(plaidId));
|
||||
ArgumentNullException.ThrowIfNull(plaidName, nameof(plaidName));
|
||||
ArgumentNullException.ThrowIfNull(encryptedAccessToken, nameof(encryptedAccessToken));
|
||||
|
||||
return new PlaidMetadata
|
||||
{
|
||||
PlaidId = plaidId,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace FiscalOS.Infra.Accounts.Plaid;
|
||||
|
||||
public sealed class PlaidService
|
||||
{
|
||||
private readonly PlaidClient _client;
|
||||
|
||||
private PlaidService(PlaidClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public static PlaidService From(IServiceProvider sp)
|
||||
{
|
||||
var client = sp.GetRequiredService<PlaidClient>();
|
||||
return new(client);
|
||||
}
|
||||
|
||||
public async Task<(string ItemId, string AccessToken)> ExchangeTokenAsync(string publicToken)
|
||||
{
|
||||
var ptr = await _client.ItemPublicTokenExchangeAsync(new()
|
||||
{
|
||||
PublicToken = publicToken,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (ptr.IsSuccessStatusCode is false)
|
||||
{
|
||||
throw new PlaidException("Unable to exchange public token for access token");
|
||||
}
|
||||
|
||||
return (ptr.ItemId, ptr.AccessToken);
|
||||
}
|
||||
|
||||
public async Task<ItemWithConsentFields> GetItemAsync(string accessToken)
|
||||
{
|
||||
var ar = await _client.ItemGetAsync(new()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (ar.IsSuccessStatusCode is false)
|
||||
{
|
||||
throw new PlaidException("Unable to retrieve item");
|
||||
}
|
||||
|
||||
return ar.Item;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ public static class ServiceCollectionExtensions
|
||||
{
|
||||
services.AddSingleton<IAuthorizationMiddlewareResultHandler, ProblemDetailsAuthResultHandler>();
|
||||
|
||||
services.AddHttpClient();
|
||||
services.AddSingleton(TimeProvider.System);
|
||||
services.AddSingleton<IFileSystem, FileSystem>();
|
||||
|
||||
@@ -23,6 +24,17 @@ public static class ServiceCollectionExtensions
|
||||
services.AddDbContext<AppDbContext>();
|
||||
services.AddHostedService<MigrationService>();
|
||||
|
||||
services.ConfigureOptions<PlaidClientOptionsSetup>();
|
||||
services.AddSingleton(static sp =>
|
||||
{
|
||||
var factory = sp.GetRequiredService<IHttpClientFactory>();
|
||||
var logger = sp.GetRequiredService<ILogger<PlaidClient>>();
|
||||
var clientOptions = sp.GetRequiredService<IOptions<PlaidClientOptions>>();
|
||||
var options = clientOptions.Value.ToPlaidOptions();
|
||||
return new PlaidClient(options, factory, logger);
|
||||
});
|
||||
services.AddSingleton(PlaidService.From);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Going.Plaid" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
|
||||
@@ -10,11 +10,15 @@ global using FiscalOS.Core.Authentication;
|
||||
global using FiscalOS.Core.Data;
|
||||
global using FiscalOS.Core.Identity;
|
||||
global using FiscalOS.Core.Security;
|
||||
global using FiscalOS.Infra.Accounts.Plaid;
|
||||
global using FiscalOS.Infra.Authentication;
|
||||
global using FiscalOS.Infra.Authorization;
|
||||
global using FiscalOS.Infra.Data;
|
||||
global using FiscalOS.Infra.Security;
|
||||
|
||||
global using Going.Plaid;
|
||||
global using Going.Plaid.Entity;
|
||||
|
||||
global using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
global using Microsoft.AspNetCore.Authorization;
|
||||
global using Microsoft.AspNetCore.Authorization.Policy;
|
||||
|
||||
Reference in New Issue
Block a user