feat(infra): implement plaid service
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageVersion Include="Going.Plaid" Version="6.56.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.2" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2">
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<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(
|
public static PlaidMetadata From(
|
||||||
string plaidId,
|
string? plaidId,
|
||||||
string plaidName,
|
string? plaidName,
|
||||||
string encryptedAccessToken
|
string encryptedAccessToken
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(plaidId, nameof(plaidId));
|
||||||
|
ArgumentNullException.ThrowIfNull(plaidName, nameof(plaidName));
|
||||||
|
ArgumentNullException.ThrowIfNull(encryptedAccessToken, nameof(encryptedAccessToken));
|
||||||
|
|
||||||
return new PlaidMetadata
|
return new PlaidMetadata
|
||||||
{
|
{
|
||||||
PlaidId = plaidId,
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,8 @@ public static class ServiceCollectionExtensions
|
|||||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
|
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddSingleton<IAuthorizationMiddlewareResultHandler, ProblemDetailsAuthResultHandler>();
|
services.AddSingleton<IAuthorizationMiddlewareResultHandler, ProblemDetailsAuthResultHandler>();
|
||||||
|
|
||||||
|
services.AddHttpClient();
|
||||||
services.AddSingleton(TimeProvider.System);
|
services.AddSingleton(TimeProvider.System);
|
||||||
services.AddSingleton<IFileSystem, FileSystem>();
|
services.AddSingleton<IFileSystem, FileSystem>();
|
||||||
|
|
||||||
@@ -23,6 +24,17 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddDbContext<AppDbContext>();
|
services.AddDbContext<AppDbContext>();
|
||||||
services.AddHostedService<MigrationService>();
|
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;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Going.Plaid" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||||
|
|||||||
@@ -10,11 +10,15 @@ global using FiscalOS.Core.Authentication;
|
|||||||
global using FiscalOS.Core.Data;
|
global using FiscalOS.Core.Data;
|
||||||
global using FiscalOS.Core.Identity;
|
global using FiscalOS.Core.Identity;
|
||||||
global using FiscalOS.Core.Security;
|
global using FiscalOS.Core.Security;
|
||||||
|
global using FiscalOS.Infra.Accounts.Plaid;
|
||||||
global using FiscalOS.Infra.Authentication;
|
global using FiscalOS.Infra.Authentication;
|
||||||
global using FiscalOS.Infra.Authorization;
|
global using FiscalOS.Infra.Authorization;
|
||||||
global using FiscalOS.Infra.Data;
|
global using FiscalOS.Infra.Data;
|
||||||
global using FiscalOS.Infra.Security;
|
global using FiscalOS.Infra.Security;
|
||||||
|
|
||||||
|
global using Going.Plaid;
|
||||||
|
global using Going.Plaid.Entity;
|
||||||
|
|
||||||
global using Microsoft.AspNetCore.Authentication.JwtBearer;
|
global using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
global using Microsoft.AspNetCore.Authorization;
|
global using Microsoft.AspNetCore.Authorization;
|
||||||
global using Microsoft.AspNetCore.Authorization.Policy;
|
global using Microsoft.AspNetCore.Authorization.Policy;
|
||||||
|
|||||||
Reference in New Issue
Block a user