asentrafin/backend/Services/Plaid/PlaidClient.cs

96 lines
3.2 KiB
C#

using System.Net.Http.Json;
using System.Text.Json;
using Asentrafin.Api.Configuration;
using Microsoft.Extensions.Options;
namespace Asentrafin.Api.Services.Plaid;
public class PlaidClient(HttpClient httpClient, IOptions<PlaidOptions> options) : IPlaidClient
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
private readonly PlaidOptions _options = options.Value;
public Task<JsonDocument> AccountsGetAsync(string accessToken, CancellationToken cancellationToken)
{
return PostAsync("/accounts/get", BuildAuthedRequest(accessToken), cancellationToken);
}
public Task<JsonDocument> TransactionsSyncAsync(string accessToken, string? cursor, CancellationToken cancellationToken)
{
var body = BuildAuthedRequest(accessToken);
body["cursor"] = cursor;
body["count"] = 500;
body["options"] = new Dictionary<string, object?>
{
["include_original_description"] = true,
["personal_finance_category_version"] = "v2"
};
return PostAsync("/transactions/sync", body, cancellationToken);
}
public Task<JsonDocument> InvestmentsHoldingsGetAsync(string accessToken, CancellationToken cancellationToken)
{
return PostAsync("/investments/holdings/get", BuildAuthedRequest(accessToken), cancellationToken);
}
public Task<JsonDocument> InvestmentsTransactionsGetAsync(
string accessToken,
DateOnly startDate,
DateOnly endDate,
int count,
int offset,
CancellationToken cancellationToken)
{
var body = BuildAuthedRequest(accessToken);
body["start_date"] = startDate.ToString("yyyy-MM-dd");
body["end_date"] = endDate.ToString("yyyy-MM-dd");
body["options"] = new Dictionary<string, object?>
{
["count"] = count,
["offset"] = offset
};
return PostAsync("/investments/transactions/get", body, cancellationToken);
}
private async Task<JsonDocument> PostAsync(
string path,
Dictionary<string, object?> body,
CancellationToken cancellationToken)
{
EnsureConfigured();
using var response = await httpClient.PostAsJsonAsync(path, body, JsonOptions, cancellationToken);
var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Plaid request to {path} failed with {(int)response.StatusCode}: {responseBody}",
null,
response.StatusCode);
}
return JsonDocument.Parse(responseBody);
}
private Dictionary<string, object?> BuildAuthedRequest(string accessToken)
{
return new Dictionary<string, object?>
{
["client_id"] = _options.ClientId,
["secret"] = _options.Secret,
["access_token"] = accessToken
};
}
private void EnsureConfigured()
{
if (string.IsNullOrWhiteSpace(_options.ClientId) || string.IsNullOrWhiteSpace(_options.Secret))
{
throw new InvalidOperationException("Plaid ClientId and Secret must be configured.");
}
}
}