This commit is contained in:
JiriUhlir
2026-06-18 11:05:46 +02:00
parent f44ffae9fc
commit 09db30a3ca
84 changed files with 3952 additions and 15 deletions
+42
View File
@@ -0,0 +1,42 @@
using Csob.Configuration;
using Csob.Credentials;
namespace Csob.Client;
/// <summary>
/// Scoped accessor that resolves the credentials for the current request and lazily builds a
/// single <see cref="CsobApiClient"/> (bound to the request's mutual-TLS client) shared by all
/// services handling that request.
/// </summary>
public sealed class CsobApiAccessor
{
private readonly RequestCredentialsProvider _credentialsProvider;
private readonly CsobHttpClientProvider _httpClientProvider;
private readonly CsobSettings _settings;
private CsobApiClient? _client;
public CsobApiAccessor(
RequestCredentialsProvider credentialsProvider,
CsobHttpClientProvider httpClientProvider,
CsobSettings settings)
{
_credentialsProvider = credentialsProvider;
_httpClientProvider = httpClientProvider;
_settings = settings;
}
public CsobApiClient Client
{
get
{
if (_client is null)
{
var credentials = _credentialsProvider.Resolve();
var http = _httpClientProvider.GetClient(credentials.Certificate!);
_client = new CsobApiClient(http, credentials, _settings);
}
return _client;
}
}
}
+130
View File
@@ -0,0 +1,130 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Csob.Configuration;
using Csob.Credentials;
namespace Csob.Client;
/// <summary>
/// Thin HTTP wrapper around the ČSOB PSD2 resource API for a single request. It attaches the
/// mandatory COBS headers (<c>Authorization</c>, <c>APIKEY</c>, <c>TPP-Name</c>, <c>X-Request-ID</c>,
/// <c>Date</c>, <c>User-Involved</c>), sends the call over the per-request mutual-TLS client and
/// returns the response JSON verbatim (<see cref="JsonNode"/>) so no field is lost in translation.
/// Non-success responses become a <see cref="CsobApiException"/>.
/// </summary>
public sealed class CsobApiClient
{
/// <summary>Web defaults (camelCase) match the COBS JSON contract; null properties are omitted on write.</summary>
public static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
};
private readonly HttpClient _http;
private readonly CsobCredentials _credentials;
private readonly string _baseUrl;
public CsobApiClient(HttpClient http, CsobCredentials credentials, CsobSettings settings)
{
_http = http;
_credentials = credentials;
_baseUrl = settings.ApiBaseUrl.TrimEnd('/');
}
public Task<JsonNode?> GetAsync(string path, IReadOnlyDictionary<string, string?>? query, CancellationToken ct)
=> SendAsync(HttpMethod.Get, path, query, body: null, ct);
public Task<JsonNode?> PostAsync(string path, object? body, CancellationToken ct)
=> SendAsync(HttpMethod.Post, path, query: null, body, ct);
public Task<JsonNode?> PutAsync(string path, object? body, CancellationToken ct)
=> SendAsync(HttpMethod.Put, path, query: null, body, ct);
public Task<JsonNode?> DeleteAsync(string path, CancellationToken ct)
=> SendAsync(HttpMethod.Delete, path, query: null, body: null, ct);
private async Task<JsonNode?> SendAsync(
HttpMethod method, string path, IReadOnlyDictionary<string, string?>? query, object? body, CancellationToken ct)
{
using var request = new HttpRequestMessage(method, _baseUrl + path + BuildQuery(query));
ApplyHeaders(request);
if (body is not null)
{
var json = body is JsonNode node ? node.ToJsonString(JsonOptions) : JsonSerializer.Serialize(body, JsonOptions);
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
}
using var response = await _http.SendAsync(request, ct);
var payload = await response.Content.ReadAsStringAsync(ct);
if (!response.IsSuccessStatusCode)
{
throw new CsobApiException(response.StatusCode, ParseErrorCodes(payload), payload);
}
return string.IsNullOrWhiteSpace(payload) ? null : JsonNode.Parse(payload);
}
private void ApplyHeaders(HttpRequestMessage request)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _credentials.AccessToken);
request.Headers.Date = DateTimeOffset.UtcNow;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.TryAddWithoutValidation("APIKEY", _credentials.ApiKey);
request.Headers.TryAddWithoutValidation("TPP-Name", _credentials.TppName);
request.Headers.TryAddWithoutValidation("X-Request-ID", Guid.NewGuid().ToString());
request.Headers.TryAddWithoutValidation("User-Involved", _credentials.UserInvolved ? "true" : "false");
if (!string.IsNullOrWhiteSpace(_credentials.UserIpAddress))
{
request.Headers.TryAddWithoutValidation("User-IP-Address", _credentials.UserIpAddress);
}
}
private static string BuildQuery(IReadOnlyDictionary<string, string?>? query)
{
if (query is null)
{
return string.Empty;
}
var parts = query
.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Value))
.Select(kvp => $"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value!)}")
.ToArray();
return parts.Length == 0 ? string.Empty : "?" + string.Join("&", parts);
}
/// <summary>Best-effort parse of the COBS error shape <c>{ "errors": [ { "error": "CODE" } ] }</c>.</summary>
private static IReadOnlyList<string> ParseErrorCodes(string payload)
{
if (string.IsNullOrWhiteSpace(payload))
{
return Array.Empty<string>();
}
try
{
var node = JsonNode.Parse(payload);
if (node?["errors"] is JsonArray errors)
{
return errors
.Select(e => e?["error"]?.GetValue<string>())
.Where(code => !string.IsNullOrWhiteSpace(code))
.Select(code => code!)
.ToArray();
}
}
catch (JsonException)
{
// Upstream returned a non-JSON error body; the raw payload is still carried on the exception.
}
return Array.Empty<string>();
}
}
+26
View File
@@ -0,0 +1,26 @@
using System.Net;
namespace Csob.Client;
/// <summary>
/// Raised when the ČSOB PSD2 API returns a non-success HTTP status. Carries the upstream status
/// and the raw error body so the middleware can surface it without leaking credentials.
/// </summary>
public sealed class CsobApiException : Exception
{
public HttpStatusCode StatusCode { get; }
/// <summary>Machine-readable error codes parsed from the ČSOB <c>errors[].error</c> array (best effort).</summary>
public IReadOnlyList<string> ErrorCodes { get; }
/// <summary>Raw response body (already credential-free — it is the upstream's own error payload).</summary>
public string? RawBody { get; }
public CsobApiException(HttpStatusCode statusCode, IReadOnlyList<string> errorCodes, string? rawBody)
: base($"ČSOB PSD2 API returned {(int)statusCode} ({statusCode}).")
{
StatusCode = statusCode;
ErrorCodes = errorCodes;
RawBody = rawBody;
}
}
+44
View File
@@ -0,0 +1,44 @@
namespace Csob.Client;
/// <summary>
/// Central registry of ČSOB PSD2 resource path templates (relative to <c>CSOB_API_BASE_URL</c>).
///
/// Paths follow the Czech Open Banking Standard (COBS) as implemented by ČSOB. The account-scoped
/// AISP paths and the PISP <c>/my/payments/.../sign/{signId}</c> authorization flow are confirmed
/// against the ČSOB developer portal; the remaining COBS resources use the same <c>/my/</c> prefix.
/// Keep every path here so a portal-specific correction is a single-file change.
/// </summary>
public static class CsobApiPaths
{
// ----- AISP (Account Information) -----
public const string Accounts = "/my/accounts";
public static string Account(string id) => $"/my/accounts/{Esc(id)}";
public static string Balance(string accountId) => $"/my/accounts/{Esc(accountId)}/balance";
public static string Transactions(string accountId) => $"/my/accounts/{Esc(accountId)}/transactions";
public static string AwaitingTransactions(string accountId) => $"/my/accounts/{Esc(accountId)}/transactions/awaiting";
public static string AccountStandingOrders(string accountId) => $"/my/accounts/{Esc(accountId)}/standingorders";
public static string AccountStandingOrder(string accountId, string standingOrderId) => $"/my/accounts/{Esc(accountId)}/standingorders/{Esc(standingOrderId)}";
public static string AccountDirectDebits(string accountId) => $"/my/accounts/{Esc(accountId)}/directdebits";
// ----- PISP (Payment Initiation) -----
public const string Payments = "/my/payments";
public static string Payment(string id) => $"/my/payments/{Esc(id)}";
public static string PaymentStatus(string id) => $"/my/payments/{Esc(id)}/status";
public static string PaymentSign(string id, string signId) => $"/my/payments/{Esc(id)}/sign/{Esc(signId)}";
public const string StandingOrders = "/my/standingorders";
public static string StandingOrder(string id) => $"/my/standingorders/{Esc(id)}";
public static string StandingOrderStatus(string id) => $"/my/standingorders/{Esc(id)}/status";
public static string StandingOrderSign(string id, string signId) => $"/my/standingorders/{Esc(id)}/sign/{Esc(signId)}";
public const string DirectDebits = "/my/directdebits";
public static string DirectDebit(string id) => $"/my/directdebits/{Esc(id)}";
public static string DirectDebitStatus(string id) => $"/my/directdebits/{Esc(id)}/status";
public static string DirectDebitSign(string id, string signId) => $"/my/directdebits/{Esc(id)}/sign/{Esc(signId)}";
// ----- Common (Consents) -----
public const string Consents = "/consents";
public static string Consent(string id) => $"/consents/{Esc(id)}";
private static string Esc(string segment) => Uri.EscapeDataString(segment);
}
+77
View File
@@ -0,0 +1,77 @@
using System.Collections.Concurrent;
using System.Security.Cryptography.X509Certificates;
using Csob.Configuration;
namespace Csob.Client;
/// <summary>
/// Provides <see cref="HttpClient"/> instances configured for mutual TLS with a per-request eIDAS
/// client certificate. ČSOB requires the client certificate at the transport layer, so a single
/// shared client cannot be used across tenants.
///
/// Clients are cached by certificate thumbprint and reused for connection pooling. In practice the
/// certificate identifies the TPP application (not the PSU), so the number of distinct certificates
/// is small and bounded by the set of calling tenants. Certificates and clients live in memory only
/// and never touch disk.
/// </summary>
public sealed class CsobHttpClientProvider : IDisposable
{
private readonly CsobSettings _settings;
private readonly ConcurrentDictionary<string, HttpClient> _clients = new();
private readonly object _buildLock = new();
public CsobHttpClientProvider(CsobSettings settings) => _settings = settings;
/// <summary>
/// Returns a (cached) mutual-TLS <see cref="HttpClient"/> presenting <paramref name="certificate"/>.
/// A fresh certificate instance is built per request; if an equivalent one (same thumbprint) is
/// already cached, the redundant instance is disposed so it does not leak.
/// </summary>
public HttpClient GetClient(X509Certificate2 certificate)
{
var thumbprint = certificate.Thumbprint;
if (_clients.TryGetValue(thumbprint, out var existing))
{
certificate.Dispose();
return existing;
}
lock (_buildLock)
{
if (_clients.TryGetValue(thumbprint, out existing))
{
certificate.Dispose();
return existing;
}
var client = BuildClient(certificate);
_clients[thumbprint] = client;
return client;
}
}
private HttpClient BuildClient(X509Certificate2 certificate)
{
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
};
handler.SslOptions.ClientCertificates = new X509CertificateCollection { certificate };
return new HttpClient(handler, disposeHandler: true)
{
Timeout = TimeSpan.FromSeconds(_settings.RequestTimeoutSeconds),
};
}
public void Dispose()
{
foreach (var client in _clients.Values)
{
client.Dispose();
}
_clients.Clear();
}
}
+54
View File
@@ -0,0 +1,54 @@
namespace Csob.Configuration;
/// <summary>
/// Service configuration resolved from environment variables.
///
/// This service is a stateless, multi-tenant proxy in front of the ČSOB PSD2 (Open Banking)
/// API. Unlike the sibling iDoklad service, it holds <b>no per-client secrets</b>: the eIDAS
/// client certificate, OAuth client id/secret, access token, API key and TPP name are all
/// supplied per request as HTTP headers by the calling client service
/// (see <see cref="Credentials.CredentialConstants"/>). Only non-secret infrastructure
/// configuration (API/OAuth base URLs, app metadata, reverse-proxy prefix, timeout) lives here.
/// </summary>
public sealed class CsobSettings
{
public string AppName { get; init; } = GetEnv("APP_NAME", "ČSOB PSD2 Service");
public string AppVersion { get; init; } = GetEnv("APP_VERSION", "1.0.0");
/// <summary>Public reverse-proxy prefix (e.g. <c>/apps/csob</c>) injected by AppFactory.</summary>
public string RootPath { get; init; } = GetEnv("ROOT_PATH", string.Empty);
/// <summary>
/// Base URL of the ČSOB PSD2 resource API. Production default; override for any other
/// environment. All AISP/PISP/consent path templates are appended to this base.
/// </summary>
public string ApiBaseUrl { get; init; } =
GetEnv("CSOB_API_BASE_URL", "https://api.csob.cz/api/csob/psd2/v1");
/// <summary>
/// OAuth2 authorization endpoint (Authorization Code flow, PSU redirect). Production default;
/// verify against the current ČSOB developer portal as the host may change.
/// </summary>
public string OAuthAuthorizeUrl { get; init; } =
GetEnv("CSOB_OAUTH_AUTHORIZE_URL", "https://identita.csob.cz/mep/fs/fl/oauth2/auth");
/// <summary>
/// OAuth2 token endpoint (code-&gt;token and refresh). Production default; verify against the
/// current ČSOB developer portal.
/// </summary>
public string OAuthTokenUrl { get; init; } =
GetEnv("CSOB_OAUTH_TOKEN_URL", "https://api.csob.cz/api/csob/oauth2/v1/token");
/// <summary>Upstream HTTP request timeout in seconds.</summary>
public int RequestTimeoutSeconds { get; init; } =
ParseInt(GetEnv("CSOB_REQUEST_TIMEOUT_SECONDS", "100"), 100);
private static string GetEnv(string name, string fallback)
{
var value = Environment.GetEnvironmentVariable(name);
return string.IsNullOrWhiteSpace(value) ? fallback : value;
}
private static int ParseInt(string value, int fallback) =>
int.TryParse(value, out var parsed) && parsed > 0 ? parsed : fallback;
}
+61
View File
@@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Mvc;
using Csob.Services;
namespace Csob.Controllers;
/// <summary>
/// AISP account information. All endpoints require the ČSOB credential headers
/// (eIDAS certificate, access token, API key, TPP name). See the Swagger description for details.
/// </summary>
[ApiController]
[Route("accounts")]
[Produces("application/json")]
[Tags("AISP Accounts")]
public sealed class AccountsController : ControllerBase
{
private readonly AccountsService _service;
public AccountsController(AccountsService service) => _service = service;
/// <summary>List the PSU's payment accounts (paged).</summary>
[HttpGet]
public async Task<IActionResult> List(
[FromQuery] int? page, [FromQuery] int? size, [FromQuery] string? sort, [FromQuery] string? order, CancellationToken ct)
=> Ok(await _service.ListAsync(page, size, sort, order, ct));
/// <summary>Get the balance(s) of an account.</summary>
[HttpGet("{id}/balance")]
public async Task<IActionResult> Balance(string id, [FromQuery] string? currency, CancellationToken ct)
=> Ok(await _service.BalanceAsync(id, currency, ct));
/// <summary>List booked transactions of an account (paged, optional date range).</summary>
[HttpGet("{id}/transactions")]
public async Task<IActionResult> Transactions(
string id,
[FromQuery] int? page, [FromQuery] int? size, [FromQuery] string? sort, [FromQuery] string? order,
[FromQuery] string? dateFrom, [FromQuery] string? dateTo, CancellationToken ct)
=> Ok(await _service.TransactionsAsync(id, new TransactionQuery
{
Page = page, Size = size, Sort = sort, Order = order, DateFrom = dateFrom, DateTo = dateTo,
}, ct));
/// <summary>List awaiting (pending) transactions of an account.</summary>
[HttpGet("{id}/transactions/awaiting")]
public async Task<IActionResult> Awaiting(string id, [FromQuery] int? page, [FromQuery] int? size, CancellationToken ct)
=> Ok(await _service.AwaitingTransactionsAsync(id, page, size, ct));
/// <summary>List the account's existing standing orders.</summary>
[HttpGet("{id}/standing-orders")]
public async Task<IActionResult> StandingOrders(string id, [FromQuery] int? page, [FromQuery] int? size, CancellationToken ct)
=> Ok(await _service.StandingOrdersAsync(id, page, size, ct));
/// <summary>Get a standing-order detail for the account.</summary>
[HttpGet("{id}/standing-orders/{standingOrderId}")]
public async Task<IActionResult> StandingOrderDetail(string id, string standingOrderId, CancellationToken ct)
=> Ok(await _service.StandingOrderDetailAsync(id, standingOrderId, ct));
/// <summary>List the account's direct-debit mandates.</summary>
[HttpGet("{id}/direct-debits")]
public async Task<IActionResult> DirectDebits(string id, [FromQuery] int? page, [FromQuery] int? size, CancellationToken ct)
=> Ok(await _service.DirectDebitsAsync(id, page, size, ct));
}
+35
View File
@@ -0,0 +1,35 @@
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc;
using Csob.Services;
namespace Csob.Controllers;
/// <summary>
/// Common PSU consent lifecycle. Requires the ČSOB credential headers. The consent body is
/// forwarded to ČSOB as raw JSON (COBS consent shape).
/// </summary>
[ApiController]
[Route("consents")]
[Produces("application/json")]
[Tags("Consents")]
public sealed class ConsentsController : ControllerBase
{
private readonly ConsentsService _service;
public ConsentsController(ConsentsService service) => _service = service;
/// <summary>Create a consent.</summary>
[HttpPost]
public async Task<IActionResult> Create([FromBody] JsonNode request, CancellationToken ct)
=> Ok(await _service.CreateAsync(request, ct));
/// <summary>Get a consent detail.</summary>
[HttpGet("{id}")]
public async Task<IActionResult> Detail(string id, CancellationToken ct)
=> Ok(await _service.DetailAsync(id, ct));
/// <summary>Revoke a consent.</summary>
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(string id, CancellationToken ct)
=> Ok(await _service.DeleteAsync(id, ct));
}
+56
View File
@@ -0,0 +1,56 @@
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Csob.Services;
namespace Csob.Controllers;
/// <summary>
/// PISP direct-debit mandate initiation and its authorization (sign) flow. Requires the ČSOB
/// credential headers. The mandate body is forwarded to ČSOB as raw JSON (COBS direct-debit shape).
/// </summary>
[ApiController]
[Route("direct-debits")]
[Produces("application/json")]
[Tags("PISP Direct debits")]
public sealed class DirectDebitsController : ControllerBase
{
private readonly DirectDebitsService _service;
public DirectDebitsController(DirectDebitsService service) => _service = service;
/// <summary>Initiate a direct-debit mandate.</summary>
[HttpPost]
public async Task<IActionResult> Initiate([FromBody] JsonNode request, CancellationToken ct)
=> Ok(await _service.InitiateAsync(request, ct));
/// <summary>Get the direct-debit mandate detail.</summary>
[HttpGet("{id}")]
public async Task<IActionResult> Detail(string id, CancellationToken ct)
=> Ok(await _service.DetailAsync(id, ct));
/// <summary>Get the direct-debit instruction status.</summary>
[HttpGet("{id}/status")]
public async Task<IActionResult> Status(string id, CancellationToken ct)
=> Ok(await _service.StatusAsync(id, ct));
/// <summary>Revoke a direct-debit mandate.</summary>
[HttpDelete("{id}")]
public async Task<IActionResult> Cancel(string id, CancellationToken ct)
=> Ok(await _service.CancelAsync(id, ct));
/// <summary>Start the authorization (SCA) of a direct-debit mandate. Returns the PSU redirect details.</summary>
[HttpPost("{id}/sign/{signId}")]
public async Task<IActionResult> StartSign(string id, string signId, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] JsonNode? body, CancellationToken ct)
=> Ok(await _service.StartSignAsync(id, signId, body, ct));
/// <summary>Get the current state of an authorization (sign) transaction.</summary>
[HttpGet("{id}/sign/{signId}")]
public async Task<IActionResult> SignStatus(string id, string signId, CancellationToken ct)
=> Ok(await _service.SignStatusAsync(id, signId, ct));
/// <summary>Finalize an authorization (sign) transaction.</summary>
[HttpPut("{id}/sign/{signId}")]
public async Task<IActionResult> FinalizeSign(string id, string signId, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] JsonNode? body, CancellationToken ct)
=> Ok(await _service.FinalizeSignAsync(id, signId, body, ct));
}
+45
View File
@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Mvc;
using Csob.Configuration;
namespace Csob.Controllers;
/// <summary>Service metadata endpoints. These do not require ČSOB credentials.</summary>
[ApiController]
[Produces("application/json")]
[Tags("Meta")]
public sealed class MetaController : ControllerBase
{
private readonly CsobSettings _settings;
public MetaController(CsobSettings settings) => _settings = settings;
/// <summary>Liveness probe.</summary>
[HttpGet("/health")]
public IActionResult Health() => Ok(new { status = "ok" });
/// <summary>Service name, version and the configured upstream endpoints (no secrets).</summary>
[HttpGet("/version")]
public IActionResult Version() => Ok(new
{
app = _settings.AppName,
version = _settings.AppVersion,
language = "dotnet",
api = "ČSOB PSD2 (Czech Open Banking Standard / COBS)",
root_path = _settings.RootPath,
});
/// <summary>
/// Reports the non-secret configuration. This service is stateless and multi-tenant: it holds
/// no credentials, so there is nothing per-client to report — every secret is supplied per
/// request via headers.
/// </summary>
[HttpGet("/status")]
public IActionResult Status() => Ok(new
{
stateless = true,
api_base_url = _settings.ApiBaseUrl,
oauth_authorize_url = _settings.OAuthAuthorizeUrl,
oauth_token_url = _settings.OAuthTokenUrl,
request_timeout_seconds = _settings.RequestTimeoutSeconds,
});
}
+39
View File
@@ -0,0 +1,39 @@
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc;
using Csob.Models;
using Csob.Services;
namespace Csob.Controllers;
/// <summary>
/// OAuth2 Authorization Code helper for the ČSOB PSD2 PSU consent flow. Build the authorization URL,
/// redirect the PSU to it, then exchange the returned code for tokens. The token/refresh calls run
/// over mutual TLS, so they require the <c>X-CSOB-Certificate</c> header; client id/secret are also
/// supplied per request (this service is multi-tenant).
/// </summary>
[ApiController]
[Route("oauth")]
[Produces("application/json")]
[Tags("OAuth2")]
public sealed class OAuthController : ControllerBase
{
private readonly OAuthService _service;
public OAuthController(OAuthService service) => _service = service;
/// <summary>Build the ČSOB authorization URL to which the PSU must be redirected.</summary>
[HttpGet("authorization-url")]
public IActionResult AuthorizationUrl(
[FromQuery] string redirectUri, [FromQuery] string? scope, [FromQuery] string? state)
=> Ok(_service.BuildAuthorizationUrl(redirectUri, scope, state));
/// <summary>Exchange an authorization code for an access/refresh token (mutual TLS).</summary>
[HttpPost("token")]
public async Task<ActionResult<JsonNode>> Token([FromBody] TokenExchangeRequest request, CancellationToken ct)
=> Ok(await _service.ExchangeCodeAsync(request, ct));
/// <summary>Refresh an access token using a refresh token (mutual TLS).</summary>
[HttpPost("refresh")]
public async Task<ActionResult<JsonNode>> Refresh([FromBody] TokenRefreshRequest request, CancellationToken ct)
=> Ok(await _service.RefreshAsync(request, ct));
}
+58
View File
@@ -0,0 +1,58 @@
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Csob.Models;
using Csob.Services;
namespace Csob.Controllers;
/// <summary>
/// PISP single payment initiation and its authorization (sign) flow. Requires the ČSOB credential
/// headers. After <c>POST /payments</c> the response carries <c>signInfo.signId</c>; use it with the
/// sign endpoints to drive Strong Customer Authentication (SCA).
/// </summary>
[ApiController]
[Route("payments")]
[Produces("application/json")]
[Tags("PISP Payments")]
public sealed class PaymentsController : ControllerBase
{
private readonly PaymentsService _service;
public PaymentsController(PaymentsService service) => _service = service;
/// <summary>Initiate a domestic (DMCT) or SEPA (ESCT) payment.</summary>
[HttpPost]
public async Task<IActionResult> Initiate([FromBody] PaymentInitiationRequest request, CancellationToken ct)
=> Ok(await _service.InitiateAsync(request, ct));
/// <summary>Get the full payment detail.</summary>
[HttpGet("{id}")]
public async Task<IActionResult> Detail(string id, CancellationToken ct)
=> Ok(await _service.DetailAsync(id, ct));
/// <summary>Get the payment instruction status.</summary>
[HttpGet("{id}/status")]
public async Task<IActionResult> Status(string id, CancellationToken ct)
=> Ok(await _service.StatusAsync(id, ct));
/// <summary>Cancel a not-yet-authorized payment.</summary>
[HttpDelete("{id}")]
public async Task<IActionResult> Cancel(string id, CancellationToken ct)
=> Ok(await _service.CancelAsync(id, ct));
/// <summary>Start the authorization (SCA) of a payment. Returns the PSU redirect details.</summary>
[HttpPost("{id}/sign/{signId}")]
public async Task<IActionResult> StartSign(string id, string signId, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] JsonNode? body, CancellationToken ct)
=> Ok(await _service.StartSignAsync(id, signId, body, ct));
/// <summary>Get the current state of an authorization (sign) transaction.</summary>
[HttpGet("{id}/sign/{signId}")]
public async Task<IActionResult> SignStatus(string id, string signId, CancellationToken ct)
=> Ok(await _service.SignStatusAsync(id, signId, ct));
/// <summary>Finalize an authorization (sign) transaction.</summary>
[HttpPut("{id}/sign/{signId}")]
public async Task<IActionResult> FinalizeSign(string id, string signId, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] JsonNode? body, CancellationToken ct)
=> Ok(await _service.FinalizeSignAsync(id, signId, body, ct));
}
+54
View File
@@ -0,0 +1,54 @@
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Csob.Models;
using Csob.Services;
namespace Csob.Controllers;
/// <summary>PISP standing-order initiation and its authorization (sign) flow. Requires the ČSOB credential headers.</summary>
[ApiController]
[Route("standing-orders")]
[Produces("application/json")]
[Tags("PISP Standing orders")]
public sealed class StandingOrdersController : ControllerBase
{
private readonly StandingOrdersService _service;
public StandingOrdersController(StandingOrdersService service) => _service = service;
/// <summary>Initiate a standing order.</summary>
[HttpPost]
public async Task<IActionResult> Initiate([FromBody] StandingOrderInitiationRequest request, CancellationToken ct)
=> Ok(await _service.InitiateAsync(request, ct));
/// <summary>Get the standing-order detail.</summary>
[HttpGet("{id}")]
public async Task<IActionResult> Detail(string id, CancellationToken ct)
=> Ok(await _service.DetailAsync(id, ct));
/// <summary>Get the standing-order instruction status.</summary>
[HttpGet("{id}/status")]
public async Task<IActionResult> Status(string id, CancellationToken ct)
=> Ok(await _service.StatusAsync(id, ct));
/// <summary>Cancel a standing order.</summary>
[HttpDelete("{id}")]
public async Task<IActionResult> Cancel(string id, CancellationToken ct)
=> Ok(await _service.CancelAsync(id, ct));
/// <summary>Start the authorization (SCA) of a standing order. Returns the PSU redirect details.</summary>
[HttpPost("{id}/sign/{signId}")]
public async Task<IActionResult> StartSign(string id, string signId, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] JsonNode? body, CancellationToken ct)
=> Ok(await _service.StartSignAsync(id, signId, body, ct));
/// <summary>Get the current state of an authorization (sign) transaction.</summary>
[HttpGet("{id}/sign/{signId}")]
public async Task<IActionResult> SignStatus(string id, string signId, CancellationToken ct)
=> Ok(await _service.SignStatusAsync(id, signId, ct));
/// <summary>Finalize an authorization (sign) transaction.</summary>
[HttpPut("{id}/sign/{signId}")]
public async Task<IActionResult> FinalizeSign(string id, string signId, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] JsonNode? body, CancellationToken ct)
=> Ok(await _service.FinalizeSignAsync(id, signId, body, ct));
}
+44
View File
@@ -0,0 +1,44 @@
namespace Csob.Credentials;
/// <summary>
/// Names of the HTTP headers that carry per-request ČSOB credentials and context.
///
/// This service is multi-tenant: it stores no credentials itself. Every sensitive value is
/// supplied per request in a header (never the query string or body) and is forwarded to ČSOB.
/// Headers must therefore only be sent over TLS. Nothing here is logged or persisted.
/// </summary>
public static class CredentialConstants
{
/// <summary>
/// eIDAS client certificate (QWAC) as a Base64-encoded PKCS#12 / PFX bundle, including the
/// private key and the full chain. Used to establish the mutual-TLS connection to ČSOB.
/// Analogous to Node's <c>https.Agent({ pfx, passphrase })</c>.
/// </summary>
public const string CertificateHeader = "X-CSOB-Certificate";
/// <summary>Optional passphrase protecting the PFX in <see cref="CertificateHeader"/>.</summary>
public const string CertificatePasswordHeader = "X-CSOB-Certificate-Password";
/// <summary>OAuth2 Bearer access token obtained for the PSU; forwarded as <c>Authorization: Bearer</c>.</summary>
public const string AccessTokenHeader = "X-Access-Token";
/// <summary>ČSOB application API key; forwarded as the <c>APIKEY</c> header.</summary>
public const string ApiKeyHeader = "X-API-Key";
/// <summary>TPP (third-party provider) organisation name; forwarded as the <c>TPP-Name</c> header.</summary>
public const string TppNameHeader = "X-TPP-Name";
/// <summary>OAuth2 client id of the registered TPP application (used by the OAuth helper endpoints).</summary>
public const string ClientIdHeader = "X-CSOB-Client-Id";
/// <summary>OAuth2 client secret of the registered TPP application (used by the OAuth helper endpoints).</summary>
public const string ClientSecretHeader = "X-CSOB-Client-Secret";
// Optional PSU (end-user) context, forwarded verbatim to ČSOB when present.
/// <summary>Whether the PSU is online/involved in the request; forwarded as <c>User-Involved</c> (default false).</summary>
public const string UserInvolvedHeader = "X-User-Involved";
/// <summary>PSU IP address; forwarded as <c>User-IP-Address</c>.</summary>
public const string UserIpAddressHeader = "X-User-IP-Address";
}
+31
View File
@@ -0,0 +1,31 @@
using System.Security.Cryptography.X509Certificates;
namespace Csob.Credentials;
/// <summary>
/// Fully resolved set of per-request credentials and PSU context used to call the ČSOB PSD2 API.
/// Built from request headers by <see cref="RequestCredentialsProvider"/>; never logged.
/// </summary>
public sealed record CsobCredentials
{
/// <summary>OAuth2 Bearer access token (forwarded as <c>Authorization: Bearer</c>).</summary>
public required string AccessToken { get; init; }
/// <summary>ČSOB application API key (forwarded as <c>APIKEY</c>).</summary>
public required string ApiKey { get; init; }
/// <summary>TPP organisation name (forwarded as <c>TPP-Name</c>).</summary>
public required string TppName { get; init; }
/// <summary>
/// eIDAS client certificate (with private key) for mutual TLS. Optional at the type level so
/// metadata endpoints can resolve context, but required for any real upstream call.
/// </summary>
public X509Certificate2? Certificate { get; init; }
/// <summary>Whether the PSU is online for this request (<c>User-Involved</c>); defaults to false.</summary>
public bool UserInvolved { get; init; }
/// <summary>Optional PSU IP address (<c>User-IP-Address</c>).</summary>
public string? UserIpAddress { get; init; }
}
@@ -0,0 +1,16 @@
namespace Csob.Credentials;
/// <summary>
/// Raised when a request does not provide the credential headers required to call ČSOB.
/// Translated to HTTP 401 by the exception-handling middleware.
/// </summary>
public sealed class MissingCredentialsException : Exception
{
public IReadOnlyList<string> MissingHeaders { get; }
public MissingCredentialsException(IReadOnlyList<string> missingHeaders)
: base("Incomplete ČSOB credentials. Provide the missing values as request headers (TLS only).")
{
MissingHeaders = missingHeaders;
}
}
+97
View File
@@ -0,0 +1,97 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace Csob.Credentials;
/// <summary>
/// Resolves the ČSOB credentials and PSU context for the current request, exclusively from HTTP
/// headers (this service stores no secrets). Missing required headers produce a 401; a malformed
/// certificate or wrong passphrase produces a 400 (via <see cref="CryptographicException"/>).
/// </summary>
public sealed class RequestCredentialsProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public RequestCredentialsProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>Reads a single request header, returning null when absent or blank.</summary>
public string? Header(string name)
{
var headers = _httpContextAccessor.HttpContext?.Request.Headers;
if (headers is not null && headers.TryGetValue(name, out var value))
{
var raw = value.ToString();
if (!string.IsNullOrWhiteSpace(raw))
{
return raw;
}
}
return null;
}
/// <summary>
/// Builds the eIDAS client certificate from the Base64 PFX header (+ optional passphrase).
/// Returns null when no certificate header is present.
/// </summary>
/// <exception cref="CryptographicException">The header is not valid Base64 or the PFX/passphrase is invalid.</exception>
public X509Certificate2? TryBuildCertificate()
{
var base64 = Header(CredentialConstants.CertificateHeader);
if (base64 is null)
{
return null;
}
byte[] raw;
try
{
raw = Convert.FromBase64String(base64.Trim());
}
catch (FormatException ex)
{
throw new CryptographicException($"{CredentialConstants.CertificateHeader} is not valid Base64.", ex);
}
var password = Header(CredentialConstants.CertificatePasswordHeader);
// EphemeralKeySet keeps the private key in memory only — never written to the machine key store / disk.
return new X509Certificate2(raw, password, X509KeyStorageFlags.EphemeralKeySet);
}
/// <summary>
/// Resolves the full credential set required for an AISP/PISP/consent call. Throws
/// <see cref="MissingCredentialsException"/> if any required header is absent.
/// </summary>
public CsobCredentials Resolve()
{
var accessToken = Header(CredentialConstants.AccessTokenHeader);
var apiKey = Header(CredentialConstants.ApiKeyHeader);
var tppName = Header(CredentialConstants.TppNameHeader);
var certificate = TryBuildCertificate();
var missing = new List<string>();
if (string.IsNullOrWhiteSpace(accessToken)) missing.Add(CredentialConstants.AccessTokenHeader);
if (string.IsNullOrWhiteSpace(apiKey)) missing.Add(CredentialConstants.ApiKeyHeader);
if (string.IsNullOrWhiteSpace(tppName)) missing.Add(CredentialConstants.TppNameHeader);
if (certificate is null) missing.Add(CredentialConstants.CertificateHeader);
if (missing.Count > 0)
{
certificate?.Dispose();
throw new MissingCredentialsException(missing);
}
return new CsobCredentials
{
AccessToken = accessToken!,
ApiKey = apiKey!,
TppName = tppName!,
Certificate = certificate,
UserInvolved = string.Equals(Header(CredentialConstants.UserInvolvedHeader), "true", StringComparison.OrdinalIgnoreCase),
UserIpAddress = Header(CredentialConstants.UserIpAddressHeader),
};
}
}
+8
View File
@@ -3,5 +3,13 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>Csob</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- 1591: missing XML doc comment. We document public endpoints/models but not every member. -->
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,70 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Csob.Controllers;
using Csob.Credentials;
namespace Csob.Infrastructure;
/// <summary>
/// Documents the per-request credential headers in Swagger. Metadata endpoints need none; the
/// OAuth helper needs the certificate + client id/secret; every other (AISP/PISP/consent) endpoint
/// needs the certificate + access token + API key + TPP name. Headers are marked optional at the
/// schema level (the service validates them at runtime) but the descriptions state what is required.
/// </summary>
public sealed class CredentialHeadersOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var declaringType = context.MethodInfo.DeclaringType;
// Metadata endpoints (health/version/status) do not talk to ČSOB.
if (declaringType == typeof(MetaController))
{
return;
}
operation.Parameters ??= new List<OpenApiParameter>();
AddHeader(operation, CredentialConstants.CertificateHeader,
"eIDAS client certificate (QWAC) as a Base64-encoded PFX/PKCS#12 bundle incl. private key and chain. Used for mutual TLS to ČSOB. SENSITIVE — TLS only.");
AddHeader(operation, CredentialConstants.CertificatePasswordHeader,
"Passphrase protecting the PFX in X-CSOB-Certificate (omit if the PFX has no password). SENSITIVE.");
if (declaringType == typeof(OAuthController))
{
AddHeader(operation, CredentialConstants.ClientIdHeader,
"OAuth2 client id of the registered TPP application. Required.");
AddHeader(operation, CredentialConstants.ClientSecretHeader,
"OAuth2 client secret of the registered TPP application. Required for token/refresh. SENSITIVE.");
return;
}
AddHeader(operation, CredentialConstants.AccessTokenHeader,
"OAuth2 Bearer access token obtained for the PSU. Forwarded as 'Authorization: Bearer'. Required. SENSITIVE.");
AddHeader(operation, CredentialConstants.ApiKeyHeader,
"ČSOB application API key. Forwarded as 'APIKEY'. Required.");
AddHeader(operation, CredentialConstants.TppNameHeader,
"TPP organisation name. Forwarded as 'TPP-Name'. Required.");
AddHeader(operation, CredentialConstants.UserInvolvedHeader,
"Optional. 'true' if the PSU is online for this request (forwarded as 'User-Involved'). Defaults to false.", example: "false");
AddHeader(operation, CredentialConstants.UserIpAddressHeader,
"Optional. PSU IP address (forwarded as 'User-IP-Address').");
}
private static void AddHeader(OpenApiOperation operation, string name, string description, string? example = null)
{
operation.Parameters.Add(new OpenApiParameter
{
Name = name,
In = ParameterLocation.Header,
Required = false,
Description = description,
Schema = new OpenApiSchema
{
Type = "string",
Example = example is null ? null : new OpenApiString(example),
},
});
}
}
@@ -0,0 +1,127 @@
using System.Net;
using System.Security.Cryptography;
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc;
using Csob.Client;
using Csob.Credentials;
namespace Csob.Infrastructure;
/// <summary>
/// Translates domain exceptions into JSON <see cref="ProblemDetails"/> responses. Credentials are
/// never logged — only upstream status codes and error codes (the upstream's own payload). Errors
/// are always logged (no silent failures).
/// </summary>
public sealed class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (MissingCredentialsException ex)
{
await WriteProblem(context, HttpStatusCode.Unauthorized, ex.Message, new Dictionary<string, object?>
{
["missingHeaders"] = ex.MissingHeaders,
});
}
catch (CryptographicException ex)
{
// Invalid Base64 PFX or wrong passphrase in the certificate header. Do not log the value.
_logger.LogWarning("Client certificate could not be loaded: {Message}", ex.Message);
await WriteProblem(context, HttpStatusCode.BadRequest,
"The provided client certificate could not be loaded. Check X-CSOB-Certificate (Base64 PFX) and X-CSOB-Certificate-Password.", null);
}
catch (CsobApiException ex)
{
_logger.LogWarning("ČSOB PSD2 API call failed: {Status} {Codes}", (int)ex.StatusCode, string.Join(",", ex.ErrorCodes));
await WriteProblem(context, ex.StatusCode, ex.Message, new Dictionary<string, object?>
{
["csobErrorCodes"] = ex.ErrorCodes,
["upstreamBody"] = ParseBody(ex.RawBody),
});
}
catch (TaskCanceledException) when (!context.RequestAborted.IsCancellationRequested)
{
_logger.LogWarning("ČSOB PSD2 API call timed out.");
await WriteProblem(context, HttpStatusCode.GatewayTimeout, "The ČSOB PSD2 API did not respond in time.", null);
}
catch (HttpRequestException ex)
{
// Network failure, or the mutual-TLS handshake was rejected (e.g. wrong/expired eIDAS certificate).
_logger.LogWarning(ex, "ČSOB PSD2 API unreachable or TLS handshake failed.");
await WriteProblem(context, HttpStatusCode.BadGateway,
"The ČSOB PSD2 API is unreachable or the mutual-TLS handshake failed.", null);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled error while processing the request.");
await WriteProblem(context, HttpStatusCode.InternalServerError, "An unexpected error occurred.", null);
}
}
private static JsonNode? ParseBody(string? rawBody)
{
if (string.IsNullOrWhiteSpace(rawBody))
{
return null;
}
try
{
return JsonNode.Parse(rawBody);
}
catch (System.Text.Json.JsonException)
{
return JsonValue.Create(rawBody);
}
}
private static async Task WriteProblem(HttpContext context, HttpStatusCode status, string detail, IDictionary<string, object?>? extensions)
{
if (context.Response.HasStarted)
{
return;
}
var problem = new ProblemDetails
{
Status = (int)status,
Title = ReasonPhrase(status),
Detail = detail,
};
if (extensions is not null)
{
foreach (var (key, value) in extensions)
{
problem.Extensions[key] = value;
}
}
context.Response.Clear();
context.Response.StatusCode = (int)status;
context.Response.ContentType = "application/problem+json";
await context.Response.WriteAsJsonAsync(problem);
}
private static string ReasonPhrase(HttpStatusCode status) => status switch
{
HttpStatusCode.Unauthorized => "Unauthorized",
HttpStatusCode.BadRequest => "Bad Request",
HttpStatusCode.BadGateway => "Upstream ČSOB PSD2 API error",
HttpStatusCode.GatewayTimeout => "Upstream ČSOB PSD2 API timeout",
_ => status.ToString(),
};
}
+91
View File
@@ -0,0 +1,91 @@
using System.Text.Json.Nodes;
namespace Csob.Models;
// Reusable building blocks shared by the typed PISP request bodies. Field names follow the COBS
// JSON contract (camelCase via System.Text.Json web defaults). Deeply variable sub-trees (party
// identification, structured remittance) are typed as JsonNode so any valid COBS payload passes
// through unchanged.
/// <summary>Monetary amount with ISO 4217 currency.</summary>
public sealed class Amount
{
public decimal Value { get; set; }
public string Currency { get; set; } = "CZK";
}
/// <summary>Wrapper matching the <c>amount</c> object whose <c>instructedAmount</c> holds the value/currency.</summary>
public sealed class AmountContainer
{
public Amount InstructedAmount { get; set; } = new();
}
/// <summary>Account number identification. For domestic/SEPA writes only <c>iban</c> is required.</summary>
public sealed class AccountIdentification
{
public string? Iban { get; set; }
public string? Other { get; set; }
}
/// <summary>Account reference with optional currency (<c>debtorAccount</c>/<c>creditorAccount</c>).</summary>
public sealed class AccountReference
{
public AccountIdentification Identification { get; set; } = new();
public string? Currency { get; set; }
}
public sealed class ServiceLevel
{
/// <summary>Payment scheme: DMCT (domestic), ESCT (SEPA), XBCT (cross-border), EXCT, NXCT.</summary>
public string? Code { get; set; }
}
public sealed class PaymentTypeInformation
{
/// <summary>NORM (default), HIGH (express) or INST (instant).</summary>
public string? InstructionPriority { get; set; }
public ServiceLevel? ServiceLevel { get; set; }
}
public sealed class FinancialInstitutionIdentification
{
public string? Bic { get; set; }
}
public sealed class Agent
{
public FinancialInstitutionIdentification? FinancialInstitutionIdentification { get; set; }
}
public sealed class PostalAddress
{
public string? StreetName { get; set; }
public string? BuildingNumber { get; set; }
public string? PostCode { get; set; }
public string? TownName { get; set; }
public string? Country { get; set; }
}
/// <summary>A party (debtor/creditor/ultimate*). Identification is variable, so kept as raw JSON.</summary>
public sealed class Party
{
public string? Name { get; set; }
public PostalAddress? PostalAddress { get; set; }
public JsonNode? Identification { get; set; }
}
/// <summary>
/// Remittance information. <c>unstructured</c> is free text (Czech symbols may be encoded as
/// <c>/VS/.../SS/.../KS/...</c>); <c>structured</c> varies (its <c>reference</c> may be a string or
/// an array) so it is passed through as raw JSON.
/// </summary>
public sealed class RemittanceInformation
{
public string? Unstructured { get; set; }
public JsonNode? Structured { get; set; }
}
public sealed class Purpose
{
public string? Proprietary { get; set; }
}
+25
View File
@@ -0,0 +1,25 @@
namespace Csob.Models;
/// <summary>Body for <c>POST /oauth/token</c> (Authorization Code grant). Secrets travel in headers.</summary>
public sealed class TokenExchangeRequest
{
/// <summary>Authorization code returned to the redirect URI after PSU consent.</summary>
public string Code { get; set; } = string.Empty;
/// <summary>Redirect URI registered for the TPP app; must match the one used to obtain the code.</summary>
public string RedirectUri { get; set; } = string.Empty;
}
/// <summary>Body for <c>POST /oauth/refresh</c> (Refresh Token grant).</summary>
public sealed class TokenRefreshRequest
{
public string RefreshToken { get; set; } = string.Empty;
}
/// <summary>Response of <c>GET /oauth/authorization-url</c>.</summary>
public sealed class AuthorizationUrlResponse
{
/// <summary>Fully-built ČSOB authorization URL to which the PSU must be redirected.</summary>
public string AuthorizationUrl { get; set; } = string.Empty;
/// <summary>The opaque <c>state</c> value echoed back on the redirect (CSRF protection).</summary>
public string? State { get; set; }
}
+38
View File
@@ -0,0 +1,38 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Csob.Models;
public sealed class PaymentIdentification
{
/// <summary>Unique instruction id assigned by the TPP (idempotency key). Max 35 chars.</summary>
public string? InstructionIdentification { get; set; }
public string? EndToEndIdentification { get; set; }
}
/// <summary>
/// Payment initiation request (<c>POST /my/payments</c>). Covers domestic (DMCT) and SEPA (ESCT)
/// payments — SEPA-only fields (creditor address, agent, ultimate parties, purpose) are optional.
/// Any additional COBS field not modelled here is preserved via <see cref="AdditionalData"/>.
/// </summary>
public sealed class PaymentInitiationRequest
{
public PaymentIdentification? PaymentIdentification { get; set; }
public PaymentTypeInformation? PaymentTypeInformation { get; set; }
public AmountContainer Amount { get; set; } = new();
public string? RequestedExecutionDate { get; set; }
public Party? UltimateDebtor { get; set; }
public AccountReference DebtorAccount { get; set; } = new();
public Agent? CreditorAgent { get; set; }
public Party? Creditor { get; set; }
public AccountReference CreditorAccount { get; set; } = new();
public Party? UltimateCreditor { get; set; }
public RemittanceInformation? RemittanceInformation { get; set; }
public Purpose? Purpose { get; set; }
/// <summary>Any COBS fields not explicitly modelled are forwarded to ČSOB unchanged.</summary>
[JsonExtensionData]
public Dictionary<string, JsonElement>? AdditionalData { get; set; }
}
+48
View File
@@ -0,0 +1,48 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace Csob.Models;
public sealed class StandingOrderIdentification
{
public string? InstructionIdentification { get; set; }
public string? TransactionIdentification { get; set; }
}
public sealed class StandingOrderExecution
{
/// <summary>DAILY, WEEKLY, MONTHLY, BI_MONTHLY, QUARTERLY, HALFYEARLY, YEARLY, SINGLE, IRREGULAR.</summary>
public string? Interval { get; set; }
/// <summary>Day within the interval (e.g. day-of-month "25").</summary>
public string? IntervalDue { get; set; }
/// <summary>e.g. MAX_AMOUNT_EXCEEDED, UNTIL_CANCELLATION.</summary>
public string? Mode { get; set; }
public string? ModeDue { get; set; }
}
public sealed class StandingOrderDetail
{
public string? Alias { get; set; }
public StandingOrderExecution? Execution { get; set; }
/// <summary>Optional <c>exceptions</c> block (stoppages/breaks); shape varies, kept as raw JSON.</summary>
public JsonNode? Exceptions { get; set; }
/// <summary>Optional <c>validity</c> block (lastExecutionDate/maxAmount); kept as raw JSON.</summary>
public JsonNode? Validity { get; set; }
}
/// <summary>Standing-order initiation request (<c>POST /my/standingorders</c>).</summary>
public sealed class StandingOrderInitiationRequest
{
public StandingOrderIdentification? StandingOrderIdentification { get; set; }
public PaymentTypeInformation? PaymentTypeInformation { get; set; }
public AmountContainer Amount { get; set; } = new();
public string? RequestedExecutionDate { get; set; }
public StandingOrderDetail? StandingOrder { get; set; }
public AccountReference DebtorAccount { get; set; } = new();
public AccountReference CreditorAccount { get; set; } = new();
public RemittanceInformation? RemittanceInformation { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement>? AdditionalData { get; set; }
}
+106 -12
View File
@@ -1,22 +1,116 @@
using System.Text.Json.Serialization;
using Microsoft.OpenApi.Models;
using Csob.Client;
using Csob.Configuration;
using Csob.Credentials;
using Csob.Infrastructure;
using Csob.Services;
var builder = WebApplication.CreateBuilder(args);
// Configuration resolved from environment variables (non-secret infra config only).
var settings = new CsobSettings();
builder.Services.AddSingleton(settings);
// The eIDAS client certificate is supplied per request as a Base64 PFX header, which is large.
// Raise Kestrel's header size limit so the certificate header is not rejected.
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestHeadersTotalSize = 1024 * 1024; // 1 MiB
});
builder.Services.AddHttpContextAccessor();
// Builds/caches mutual-TLS HttpClients by certificate thumbprint; shared across requests.
builder.Services.AddSingleton<CsobHttpClientProvider>();
// Per-request credential resolution and API access.
builder.Services.AddScoped<RequestCredentialsProvider>();
builder.Services.AddScoped<CsobApiAccessor>();
// Agenda services.
builder.Services.AddScoped<AccountsService>();
builder.Services.AddScoped<PaymentsService>();
builder.Services.AddScoped<StandingOrdersService>();
builder.Services.AddScoped<DirectDebitsService>();
builder.Services.AddScoped<ConsentsService>();
builder.Services.AddScoped<OAuthService>();
builder.Services
.AddControllers()
.AddJsonOptions(options =>
{
// camelCase (web defaults) matches the COBS JSON contract; omit null properties on write.
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = settings.AppName,
Version = settings.AppVersion,
Description =
"Multi-tenant REST integration with the ČSOB PSD2 (Open Banking) API, following the " +
"Czech Open Banking Standard (COBS). Covers AISP (account information), PISP (payment, " +
"standing-order and direct-debit initiation with the sign/SCA flow), consents and the " +
"OAuth2 Authorization Code helper.\n\n" +
"**Credentials.** This service stores no secrets — every sensitive value is supplied per " +
"request in a header and forwarded to ČSOB over TLS (never the query string or body):\n\n" +
"- `X-CSOB-Certificate` — eIDAS client certificate (QWAC) as a Base64 PFX (mutual TLS). Required.\n" +
"- `X-CSOB-Certificate-Password` — PFX passphrase (optional).\n" +
"- `X-Access-Token` — OAuth2 Bearer access token for the PSU (AISP/PISP/consents). Required.\n" +
"- `X-API-Key` — ČSOB application API key (sent upstream as `APIKEY`). Required.\n" +
"- `X-TPP-Name` — TPP organisation name (sent upstream as `TPP-Name`). Required.\n" +
"- `X-CSOB-Client-Id` / `X-CSOB-Client-Secret` — OAuth2 app credentials (OAuth endpoints only).\n\n" +
"Obtain the access token via the `/oauth/*` endpoints (Authorization Code flow with PSU redirect).",
});
options.OperationFilter<CredentialHeadersOperationFilter>();
var xmlPath = Path.Combine(AppContext.BaseDirectory, $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml");
if (File.Exists(xmlPath))
{
options.IncludeXmlComments(xmlPath, includeControllerXmlComments: true);
}
});
var app = builder.Build();
var rootPath = Environment.GetEnvironmentVariable("ROOT_PATH");
if (!string.IsNullOrWhiteSpace(rootPath))
// ROOT_PATH carries the public reverse-proxy prefix (e.g. /apps/csob). AppFactory's Caddy uses
// handle_path, which strips that prefix before the request reaches the container, so PathBase ends
// up empty and the OpenAPI server URL must come from ROOT_PATH directly (see the filter below).
var publicPrefix = string.IsNullOrWhiteSpace(settings.RootPath) ? null : "/" + settings.RootPath.Trim('/');
if (publicPrefix is not null)
{
app.UsePathBase(rootPath);
app.UsePathBase(publicPrefix);
}
app.MapGet("/", () => Results.Json(new
{
name = "csob",
service = "csob",
status = "ok"
}));
app.UseMiddleware<ExceptionHandlingMiddleware>();
app.MapGet("/health", () => Results.Json(new
// Serve the OpenAPI document under the same /docs prefix as the UI so a relative endpoint resolves
// correctly both locally and behind a reverse-proxy prefix (ROOT_PATH).
app.UseSwagger(options =>
{
status = "ok"
}));
options.RouteTemplate = "docs/{documentName}/swagger.json";
// Advertise the public prefix (e.g. /apps/csob) as the OpenAPI server so Swagger UI "Try it out"
// targets {prefix}/accounts, not the host root. The prefix comes from ROOT_PATH because
// handle_path has already stripped it from the request, leaving PathBase empty. Fall back to
// PathBase (a proxy that keeps the prefix) and finally "/".
options.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
{
var serverUrl = publicPrefix ?? (httpReq.PathBase.HasValue ? httpReq.PathBase.Value : "/");
swaggerDoc.Servers = new List<OpenApiServer> { new() { Url = serverUrl } };
});
});
app.UseSwaggerUI(options =>
{
options.RoutePrefix = "docs";
options.SwaggerEndpoint("v1/swagger.json", $"{settings.AppName} v1");
options.DocumentTitle = $"{settings.AppName} API docs";
});
app.MapControllers();
app.Run();
+85 -3
View File
@@ -1,8 +1,90 @@
# csob
.NET API služba vytvořená přes CSBot Services Portal.
Multi-tenant REST integrace na **ČSOB PSD2 (Open Banking) API** podle Czech Open Banking Standard (COBS).
.NET 8 služba běžící v AppFactory za reverse proxy `/apps/csob`.
Pokrývá **AISP** (informace o účtech), **PISP** (iniciace plateb, trvalých příkazů a inkas vč. sign/SCA flow),
**consents** a **OAuth2** helper (Authorization Code flow).
## Architektura
Služba je **bezstavový proxy** neukládá žádné credentials. Volající klientská služba předává veškeré
citlivé údaje (eIDAS certifikát, OAuth client id/secret, access token, APIKEY, TPP name) **per-request
v HTTP hlavičkách** (jen přes TLS). mTLS se na základě certifikátu z hlavičky staví per-request a klienti
se cachují podle thumbprintu certifikátu. Nic se neloguje ani neukládá na disk.
Strukturně zrcadlí sesterskou službu `idoklad` (Configuration → env, Credentials → hlavičky, Client factory
+ Accessor, Services, Controllers, Infrastructure middleware + Swagger operation filter, `/docs`).
## Endpointy
- GET /
- GET /health
Veřejně dostupné přes `https://services.csbot.cz/apps/csob/...`.
### Meta (bez credentials)
- `GET /health` liveness
- `GET /version` název/verze + API info
- `GET /status` ne-secret konfigurace (base/oauth URL, timeout)
- `GET /docs` Swagger UI
- `GET /docs/v1/swagger.json` OpenAPI dokument
### OAuth2
- `GET /oauth/authorization-url` sestaví authorize URL pro přesměrování PSU
- `POST /oauth/token` výměna `code` → access/refresh token (mTLS)
- `POST /oauth/refresh` obnova access tokenu (mTLS)
### AISP účty
- `GET /accounts`
- `GET /accounts/{id}/balance`
- `GET /accounts/{id}/transactions`
- `GET /accounts/{id}/transactions/awaiting`
- `GET /accounts/{id}/standing-orders`
- `GET /accounts/{id}/standing-orders/{standingOrderId}`
- `GET /accounts/{id}/direct-debits`
### PISP platby / trvalé příkazy / inkasa
Pro každou agendu: `POST` (iniciace), `GET /{id}` (detail), `GET /{id}/status`, `DELETE /{id}` (zrušení),
a sign/SCA flow `POST|GET|PUT /{id}/sign/{signId}`.
- `/payments`
- `/standing-orders`
- `/direct-debits`
### Consents
- `POST /consents`, `GET /consents/{id}`, `DELETE /consents/{id}`
## Credential hlavičky (per-request, jen přes TLS)
| Hlavička | Význam | Předáno do ČSOB jako |
|---|---|---|
| `X-CSOB-Certificate` | eIDAS klientský certifikát (QWAC), **Base64 PFX/PKCS#12** vč. privátního klíče a chainu | mutual TLS |
| `X-CSOB-Certificate-Password` | heslo k PFX (volitelné) | |
| `X-Access-Token` | OAuth2 Bearer access token PSU (AISP/PISP/consents) | `Authorization: Bearer` |
| `X-API-Key` | ČSOB application API key | `APIKEY` |
| `X-TPP-Name` | název TPP organizace | `TPP-Name` |
| `X-CSOB-Client-Id` / `X-CSOB-Client-Secret` | OAuth2 app credentials (jen `/oauth/*`) | OAuth form |
| `X-User-Involved`, `X-User-IP-Address` | volitelný PSU kontext | `User-Involved`, `User-IP-Address` |
Hlavičky `X-Request-ID` a `Date` generuje služba automaticky.
## Konfigurace (environment variables pouze ne-secret)
| Proměnná | Default |
|---|---|
| `APP_NAME` | `ČSOB PSD2 Service` |
| `APP_VERSION` | `1.0.0` |
| `ROOT_PATH` | (prázdné; AppFactory nastaví `/apps/csob`) |
| `CSOB_API_BASE_URL` | `https://api.csob.cz/api/csob/psd2/v1` |
| `CSOB_OAUTH_AUTHORIZE_URL` | `https://identita.csob.cz/mep/fs/fl/oauth2/auth` |
| `CSOB_OAUTH_TOKEN_URL` | `https://api.csob.cz/api/csob/oauth2/v1/token` |
| `CSOB_REQUEST_TIMEOUT_SECONDS` | `100` |
> **Žádné secrets v env.** Certifikát, client id/secret, access token a APIKEY jdou výhradně per-request hlavičkami.
## Lokální spuštění
```bash
dotnet run --project Csob.csproj
# nebo
ROOT_PATH=/apps/csob ASPNETCORE_URLS=http://0.0.0.0:8080 dotnet run
```
Podrobnosti k OAuth/SCA flow a kompletní seznam endpointů viz [documentation/](documentation/).
+65
View File
@@ -0,0 +1,65 @@
using System.Text.Json.Nodes;
using Csob.Client;
namespace Csob.Services;
/// <summary>AISP account information (accounts, balance, transactions, standing orders, direct debits).</summary>
public sealed class AccountsService
{
private readonly CsobApiAccessor _accessor;
public AccountsService(CsobApiAccessor accessor) => _accessor = accessor;
public Task<JsonNode?> ListAsync(int? page, int? size, string? sort, string? order, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.Accounts, Paging(page, size, sort, order), ct);
public Task<JsonNode?> BalanceAsync(string accountId, string? currency, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.Balance(accountId),
new Dictionary<string, string?> { ["currency"] = currency }, ct);
public Task<JsonNode?> TransactionsAsync(string accountId, TransactionQuery query, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.Transactions(accountId), query.ToDictionary(), ct);
public Task<JsonNode?> AwaitingTransactionsAsync(string accountId, int? page, int? size, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.AwaitingTransactions(accountId), Paging(page, size, null, null), ct);
public Task<JsonNode?> StandingOrdersAsync(string accountId, int? page, int? size, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.AccountStandingOrders(accountId), Paging(page, size, null, null), ct);
public Task<JsonNode?> StandingOrderDetailAsync(string accountId, string standingOrderId, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.AccountStandingOrder(accountId, standingOrderId), null, ct);
public Task<JsonNode?> DirectDebitsAsync(string accountId, int? page, int? size, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.AccountDirectDebits(accountId), Paging(page, size, null, null), ct);
private static Dictionary<string, string?> Paging(int? page, int? size, string? sort, string? order) => new()
{
["page"] = page?.ToString(),
["size"] = size?.ToString(),
["sort"] = sort,
["order"] = order,
};
}
/// <summary>Optional filters for the transactions listing (forwarded as query parameters).</summary>
public sealed class TransactionQuery
{
public int? Page { get; set; }
public int? Size { get; set; }
public string? Sort { get; set; }
public string? Order { get; set; }
/// <summary>ISO date (YYYY-MM-DD) lower bound.</summary>
public string? DateFrom { get; set; }
/// <summary>ISO date (YYYY-MM-DD) upper bound.</summary>
public string? DateTo { get; set; }
public Dictionary<string, string?> ToDictionary() => new()
{
["page"] = Page?.ToString(),
["size"] = Size?.ToString(),
["sort"] = Sort,
["order"] = Order,
["dateFrom"] = DateFrom,
["dateTo"] = DateTo,
};
}
+24
View File
@@ -0,0 +1,24 @@
using System.Text.Json.Nodes;
using Csob.Client;
namespace Csob.Services;
/// <summary>
/// Common PSU consent lifecycle (create / detail / revoke). The consent request body varies
/// across COBS profiles, so it is accepted and forwarded as raw JSON.
/// </summary>
public sealed class ConsentsService
{
private readonly CsobApiAccessor _accessor;
public ConsentsService(CsobApiAccessor accessor) => _accessor = accessor;
public Task<JsonNode?> CreateAsync(JsonNode request, CancellationToken ct)
=> _accessor.Client.PostAsync(CsobApiPaths.Consents, request, ct);
public Task<JsonNode?> DetailAsync(string id, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.Consent(id), null, ct);
public Task<JsonNode?> DeleteAsync(string id, CancellationToken ct)
=> _accessor.Client.DeleteAsync(CsobApiPaths.Consent(id), ct);
}
+36
View File
@@ -0,0 +1,36 @@
using System.Text.Json.Nodes;
using Csob.Client;
namespace Csob.Services;
/// <summary>
/// PISP direct-debit mandate initiation, detail/status, revocation and the sign (SCA) flow.
/// The mandate creation body varies across COBS profiles, so it is accepted and forwarded as raw JSON.
/// </summary>
public sealed class DirectDebitsService
{
private readonly CsobApiAccessor _accessor;
public DirectDebitsService(CsobApiAccessor accessor) => _accessor = accessor;
public Task<JsonNode?> InitiateAsync(JsonNode request, CancellationToken ct)
=> _accessor.Client.PostAsync(CsobApiPaths.DirectDebits, request, ct);
public Task<JsonNode?> DetailAsync(string id, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.DirectDebit(id), null, ct);
public Task<JsonNode?> StatusAsync(string id, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.DirectDebitStatus(id), null, ct);
public Task<JsonNode?> CancelAsync(string id, CancellationToken ct)
=> _accessor.Client.DeleteAsync(CsobApiPaths.DirectDebit(id), ct);
public Task<JsonNode?> StartSignAsync(string id, string signId, JsonNode? body, CancellationToken ct)
=> _accessor.Client.PostAsync(CsobApiPaths.DirectDebitSign(id, signId), body, ct);
public Task<JsonNode?> SignStatusAsync(string id, string signId, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.DirectDebitSign(id, signId), null, ct);
public Task<JsonNode?> FinalizeSignAsync(string id, string signId, JsonNode? body, CancellationToken ct)
=> _accessor.Client.PutAsync(CsobApiPaths.DirectDebitSign(id, signId), body, ct);
}
+114
View File
@@ -0,0 +1,114 @@
using System.Net;
using System.Text.Json.Nodes;
using Csob.Client;
using Csob.Configuration;
using Csob.Credentials;
using Csob.Models;
namespace Csob.Services;
/// <summary>
/// OAuth2 Authorization Code helper for the ČSOB PSD2 flow. Builds the PSU authorization URL and
/// exchanges/refreshes tokens against the ČSOB token endpoint. The token endpoint is behind mutual
/// TLS, so the client certificate header is required for the token/refresh calls. Client id/secret
/// are taken from per-request headers (this service is multi-tenant and stores no app credentials).
/// </summary>
public sealed class OAuthService
{
private readonly RequestCredentialsProvider _credentials;
private readonly CsobHttpClientProvider _httpClientProvider;
private readonly CsobSettings _settings;
public OAuthService(
RequestCredentialsProvider credentials,
CsobHttpClientProvider httpClientProvider,
CsobSettings settings)
{
_credentials = credentials;
_httpClientProvider = httpClientProvider;
_settings = settings;
}
/// <summary>Builds the authorization URL to which the PSU must be redirected.</summary>
public AuthorizationUrlResponse BuildAuthorizationUrl(string redirectUri, string? scope, string? state)
{
var clientId = RequireHeader(CredentialConstants.ClientIdHeader);
var query = new Dictionary<string, string?>
{
["response_type"] = "code",
["client_id"] = clientId,
["redirect_uri"] = redirectUri,
["scope"] = scope,
["state"] = state,
};
var url = _settings.OAuthAuthorizeUrl + "?" + string.Join("&", query
.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Value))
.Select(kvp => $"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value!)}"));
return new AuthorizationUrlResponse { AuthorizationUrl = url, State = state };
}
public Task<JsonNode?> ExchangeCodeAsync(TokenExchangeRequest request, CancellationToken ct)
=> PostTokenAsync(new Dictionary<string, string>
{
["grant_type"] = "authorization_code",
["code"] = request.Code,
["redirect_uri"] = request.RedirectUri,
}, ct);
public Task<JsonNode?> RefreshAsync(TokenRefreshRequest request, CancellationToken ct)
=> PostTokenAsync(new Dictionary<string, string>
{
["grant_type"] = "refresh_token",
["refresh_token"] = request.RefreshToken,
}, ct);
private async Task<JsonNode?> PostTokenAsync(Dictionary<string, string> form, CancellationToken ct)
{
var clientId = RequireHeader(CredentialConstants.ClientIdHeader);
var clientSecret = RequireHeader(CredentialConstants.ClientSecretHeader);
var certificate = _credentials.TryBuildCertificate()
?? throw new MissingCredentialsException(new[] { CredentialConstants.CertificateHeader });
form["client_id"] = clientId;
form["client_secret"] = clientSecret;
var http = _httpClientProvider.GetClient(certificate);
using var content = new FormUrlEncodedContent(form);
using var response = await http.PostAsync(_settings.OAuthTokenUrl, content, ct);
var payload = await response.Content.ReadAsStringAsync(ct);
if (!response.IsSuccessStatusCode)
{
throw new CsobApiException(response.StatusCode, ParseOAuthError(payload), payload);
}
return string.IsNullOrWhiteSpace(payload) ? null : JsonNode.Parse(payload);
}
private string RequireHeader(string name)
=> _credentials.Header(name) ?? throw new MissingCredentialsException(new[] { name });
private static IReadOnlyList<string> ParseOAuthError(string payload)
{
if (string.IsNullOrWhiteSpace(payload))
{
return Array.Empty<string>();
}
try
{
// OAuth2 errors use { "error": "...", "error_description": "..." }.
var error = JsonNode.Parse(payload)?["error"]?.GetValue<string>();
return string.IsNullOrWhiteSpace(error) ? Array.Empty<string>() : new[] { error! };
}
catch (System.Text.Json.JsonException)
{
return Array.Empty<string>();
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using System.Text.Json.Nodes;
using Csob.Client;
using Csob.Models;
namespace Csob.Services;
/// <summary>PISP single payment initiation, status/detail, cancellation and the sign (SCA) flow.</summary>
public sealed class PaymentsService
{
private readonly CsobApiAccessor _accessor;
public PaymentsService(CsobApiAccessor accessor) => _accessor = accessor;
public Task<JsonNode?> InitiateAsync(PaymentInitiationRequest request, CancellationToken ct)
=> _accessor.Client.PostAsync(CsobApiPaths.Payments, request, ct);
public Task<JsonNode?> DetailAsync(string id, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.Payment(id), null, ct);
public Task<JsonNode?> StatusAsync(string id, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.PaymentStatus(id), null, ct);
public Task<JsonNode?> CancelAsync(string id, CancellationToken ct)
=> _accessor.Client.DeleteAsync(CsobApiPaths.Payment(id), ct);
/// <summary>Starts transaction authorization (SCA). Returns the redirect details for the PSU.</summary>
public Task<JsonNode?> StartSignAsync(string id, string signId, JsonNode? body, CancellationToken ct)
=> _accessor.Client.PostAsync(CsobApiPaths.PaymentSign(id, signId), body, ct);
/// <summary>Gets the current state of an authorization (sign) transaction.</summary>
public Task<JsonNode?> SignStatusAsync(string id, string signId, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.PaymentSign(id, signId), null, ct);
/// <summary>Finalizes an authorization (sign) transaction.</summary>
public Task<JsonNode?> FinalizeSignAsync(string id, string signId, JsonNode? body, CancellationToken ct)
=> _accessor.Client.PutAsync(CsobApiPaths.PaymentSign(id, signId), body, ct);
}
+34
View File
@@ -0,0 +1,34 @@
using System.Text.Json.Nodes;
using Csob.Client;
using Csob.Models;
namespace Csob.Services;
/// <summary>PISP standing-order initiation, detail/status, cancellation and the sign (SCA) flow.</summary>
public sealed class StandingOrdersService
{
private readonly CsobApiAccessor _accessor;
public StandingOrdersService(CsobApiAccessor accessor) => _accessor = accessor;
public Task<JsonNode?> InitiateAsync(StandingOrderInitiationRequest request, CancellationToken ct)
=> _accessor.Client.PostAsync(CsobApiPaths.StandingOrders, request, ct);
public Task<JsonNode?> DetailAsync(string id, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.StandingOrder(id), null, ct);
public Task<JsonNode?> StatusAsync(string id, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.StandingOrderStatus(id), null, ct);
public Task<JsonNode?> CancelAsync(string id, CancellationToken ct)
=> _accessor.Client.DeleteAsync(CsobApiPaths.StandingOrder(id), ct);
public Task<JsonNode?> StartSignAsync(string id, string signId, JsonNode? body, CancellationToken ct)
=> _accessor.Client.PostAsync(CsobApiPaths.StandingOrderSign(id, signId), body, ct);
public Task<JsonNode?> SignStatusAsync(string id, string signId, CancellationToken ct)
=> _accessor.Client.GetAsync(CsobApiPaths.StandingOrderSign(id, signId), null, ct);
public Task<JsonNode?> FinalizeSignAsync(string id, string signId, JsonNode? body, CancellationToken ct)
=> _accessor.Client.PutAsync(CsobApiPaths.StandingOrderSign(id, signId), body, ct);
}
+106
View File
@@ -0,0 +1,106 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"Csob/1.0.0": {
"dependencies": {
"Swashbuckle.AspNetCore": "6.9.0"
},
"runtime": {
"Csob.dll": {}
}
},
"Microsoft.OpenApi/1.6.14": {
"runtime": {
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
"assemblyVersion": "1.6.14.0",
"fileVersion": "1.6.14.0"
}
}
},
"Swashbuckle.AspNetCore/6.9.0": {
"dependencies": {
"Swashbuckle.AspNetCore.Swagger": "6.9.0",
"Swashbuckle.AspNetCore.SwaggerGen": "6.9.0",
"Swashbuckle.AspNetCore.SwaggerUI": "6.9.0"
}
},
"Swashbuckle.AspNetCore.Swagger/6.9.0": {
"dependencies": {
"Microsoft.OpenApi": "1.6.14"
},
"runtime": {
"lib/net8.0/Swashbuckle.AspNetCore.Swagger.dll": {
"assemblyVersion": "6.9.0.0",
"fileVersion": "6.9.0.799"
}
}
},
"Swashbuckle.AspNetCore.SwaggerGen/6.9.0": {
"dependencies": {
"Swashbuckle.AspNetCore.Swagger": "6.9.0"
},
"runtime": {
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
"assemblyVersion": "6.9.0.0",
"fileVersion": "6.9.0.799"
}
}
},
"Swashbuckle.AspNetCore.SwaggerUI/6.9.0": {
"runtime": {
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
"assemblyVersion": "6.9.0.0",
"fileVersion": "6.9.0.799"
}
}
}
}
},
"libraries": {
"Csob/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.OpenApi/1.6.14": {
"type": "package",
"serviceable": true,
"sha512": "sha512-tTaBT8qjk3xINfESyOPE2rIellPvB7qpVqiWiyA/lACVvz+xOGiXhFUfohcx82NLbi5avzLW0lx+s6oAqQijfw==",
"path": "microsoft.openapi/1.6.14",
"hashPath": "microsoft.openapi.1.6.14.nupkg.sha512"
},
"Swashbuckle.AspNetCore/6.9.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-lvI+XHF21tkwXd2nDCLGJsdhdUYsY3Ax2fWUlvw81Oa6EedtnIAf5tThy8ZnPcz/9/TwsLgjgtX9ifOCIjbEPA==",
"path": "swashbuckle.aspnetcore/6.9.0",
"hashPath": "swashbuckle.aspnetcore.6.9.0.nupkg.sha512"
},
"Swashbuckle.AspNetCore.Swagger/6.9.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-P316kpxx5DnDvJwNWW8iTAXkh9DVenAxFGe9v4OUS0gil+vitH7F1feXhCtVeHN/616EFNTMh4pV2lcr9kkw/w==",
"path": "swashbuckle.aspnetcore.swagger/6.9.0",
"hashPath": "swashbuckle.aspnetcore.swagger.6.9.0.nupkg.sha512"
},
"Swashbuckle.AspNetCore.SwaggerGen/6.9.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FjeMR3fBzwVc5plfYjoHw9ptf8SOWMupvO9X35J5EgzT3L9dRqSxa+cBKzL8PwCyemY0xNrggQSB5+MFWx1axg==",
"path": "swashbuckle.aspnetcore.swaggergen/6.9.0",
"hashPath": "swashbuckle.aspnetcore.swaggergen.6.9.0.nupkg.sha512"
},
"Swashbuckle.AspNetCore.SwaggerUI/6.9.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0OxlWBFLl2gUESZX/K7QCTz9KctKy0VxHTvLIBcyWGD4z/fv5MCMW02qzYGcReLJr4yBnNDRzApKtLh6oBpe9A==",
"path": "swashbuckle.aspnetcore.swaggerui/6.9.0",
"hashPath": "swashbuckle.aspnetcore.swaggerui.6.9.0.nupkg.sha512"
}
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,20 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "8.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}
@@ -0,0 +1 @@
{"Version":1,"ManifestType":"Build","Endpoints":[]}
+502
View File
@@ -0,0 +1,502 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>Csob</name>
</assembly>
<members>
<member name="T:Csob.Client.CsobApiAccessor">
<summary>
Scoped accessor that resolves the credentials for the current request and lazily builds a
single <see cref="T:Csob.Client.CsobApiClient"/> (bound to the request's mutual-TLS client) shared by all
services handling that request.
</summary>
</member>
<member name="T:Csob.Client.CsobApiClient">
<summary>
Thin HTTP wrapper around the ČSOB PSD2 resource API for a single request. It attaches the
mandatory COBS headers (<c>Authorization</c>, <c>APIKEY</c>, <c>TPP-Name</c>, <c>X-Request-ID</c>,
<c>Date</c>, <c>User-Involved</c>), sends the call over the per-request mutual-TLS client and
returns the response JSON verbatim (<see cref="T:System.Text.Json.Nodes.JsonNode"/>) so no field is lost in translation.
Non-success responses become a <see cref="T:Csob.Client.CsobApiException"/>.
</summary>
</member>
<member name="F:Csob.Client.CsobApiClient.JsonOptions">
<summary>Web defaults (camelCase) match the COBS JSON contract; null properties are omitted on write.</summary>
</member>
<member name="M:Csob.Client.CsobApiClient.ParseErrorCodes(System.String)">
<summary>Best-effort parse of the COBS error shape <c>{ "errors": [ { "error": "CODE" } ] }</c>.</summary>
</member>
<member name="T:Csob.Client.CsobApiException">
<summary>
Raised when the ČSOB PSD2 API returns a non-success HTTP status. Carries the upstream status
and the raw error body so the middleware can surface it without leaking credentials.
</summary>
</member>
<member name="P:Csob.Client.CsobApiException.ErrorCodes">
<summary>Machine-readable error codes parsed from the ČSOB <c>errors[].error</c> array (best effort).</summary>
</member>
<member name="P:Csob.Client.CsobApiException.RawBody">
<summary>Raw response body (already credential-free — it is the upstream's own error payload).</summary>
</member>
<member name="T:Csob.Client.CsobApiPaths">
<summary>
Central registry of ČSOB PSD2 resource path templates (relative to <c>CSOB_API_BASE_URL</c>).
Paths follow the Czech Open Banking Standard (COBS) as implemented by ČSOB. The account-scoped
AISP paths and the PISP <c>/my/payments/.../sign/{signId}</c> authorization flow are confirmed
against the ČSOB developer portal; the remaining COBS resources use the same <c>/my/</c> prefix.
Keep every path here so a portal-specific correction is a single-file change.
</summary>
</member>
<member name="T:Csob.Client.CsobHttpClientProvider">
<summary>
Provides <see cref="T:System.Net.Http.HttpClient"/> instances configured for mutual TLS with a per-request eIDAS
client certificate. ČSOB requires the client certificate at the transport layer, so a single
shared client cannot be used across tenants.
Clients are cached by certificate thumbprint and reused for connection pooling. In practice the
certificate identifies the TPP application (not the PSU), so the number of distinct certificates
is small and bounded by the set of calling tenants. Certificates and clients live in memory only
and never touch disk.
</summary>
</member>
<member name="M:Csob.Client.CsobHttpClientProvider.GetClient(System.Security.Cryptography.X509Certificates.X509Certificate2)">
<summary>
Returns a (cached) mutual-TLS <see cref="T:System.Net.Http.HttpClient"/> presenting <paramref name="certificate"/>.
A fresh certificate instance is built per request; if an equivalent one (same thumbprint) is
already cached, the redundant instance is disposed so it does not leak.
</summary>
</member>
<member name="T:Csob.Configuration.CsobSettings">
<summary>
Service configuration resolved from environment variables.
This service is a stateless, multi-tenant proxy in front of the ČSOB PSD2 (Open Banking)
API. Unlike the sibling iDoklad service, it holds <b>no per-client secrets</b>: the eIDAS
client certificate, OAuth client id/secret, access token, API key and TPP name are all
supplied per request as HTTP headers by the calling client service
(see <see cref="T:Csob.Credentials.CredentialConstants"/>). Only non-secret infrastructure
configuration (API/OAuth base URLs, app metadata, reverse-proxy prefix, timeout) lives here.
</summary>
</member>
<member name="P:Csob.Configuration.CsobSettings.RootPath">
<summary>Public reverse-proxy prefix (e.g. <c>/apps/csob</c>) injected by AppFactory.</summary>
</member>
<member name="P:Csob.Configuration.CsobSettings.ApiBaseUrl">
<summary>
Base URL of the ČSOB PSD2 resource API. Production default; override for any other
environment. All AISP/PISP/consent path templates are appended to this base.
</summary>
</member>
<member name="P:Csob.Configuration.CsobSettings.OAuthAuthorizeUrl">
<summary>
OAuth2 authorization endpoint (Authorization Code flow, PSU redirect). Production default;
verify against the current ČSOB developer portal as the host may change.
</summary>
</member>
<member name="P:Csob.Configuration.CsobSettings.OAuthTokenUrl">
<summary>
OAuth2 token endpoint (code-&gt;token and refresh). Production default; verify against the
current ČSOB developer portal.
</summary>
</member>
<member name="P:Csob.Configuration.CsobSettings.RequestTimeoutSeconds">
<summary>Upstream HTTP request timeout in seconds.</summary>
</member>
<member name="T:Csob.Controllers.AccountsController">
<summary>
AISP account information. All endpoints require the ČSOB credential headers
(eIDAS certificate, access token, API key, TPP name). See the Swagger description for details.
</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.List(System.Nullable{System.Int32},System.Nullable{System.Int32},System.String,System.String,System.Threading.CancellationToken)">
<summary>List the PSU's payment accounts (paged).</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.Balance(System.String,System.String,System.Threading.CancellationToken)">
<summary>Get the balance(s) of an account.</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.Transactions(System.String,System.Nullable{System.Int32},System.Nullable{System.Int32},System.String,System.String,System.String,System.String,System.Threading.CancellationToken)">
<summary>List booked transactions of an account (paged, optional date range).</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.Awaiting(System.String,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>List awaiting (pending) transactions of an account.</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.StandingOrders(System.String,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>List the account's existing standing orders.</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.StandingOrderDetail(System.String,System.String,System.Threading.CancellationToken)">
<summary>Get a standing-order detail for the account.</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.DirectDebits(System.String,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>List the account's direct-debit mandates.</summary>
</member>
<member name="T:Csob.Controllers.ConsentsController">
<summary>
Common PSU consent lifecycle. Requires the ČSOB credential headers. The consent body is
forwarded to ČSOB as raw JSON (COBS consent shape).
</summary>
</member>
<member name="M:Csob.Controllers.ConsentsController.Create(System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Create a consent.</summary>
</member>
<member name="M:Csob.Controllers.ConsentsController.Detail(System.String,System.Threading.CancellationToken)">
<summary>Get a consent detail.</summary>
</member>
<member name="M:Csob.Controllers.ConsentsController.Delete(System.String,System.Threading.CancellationToken)">
<summary>Revoke a consent.</summary>
</member>
<member name="T:Csob.Controllers.DirectDebitsController">
<summary>
PISP direct-debit mandate initiation and its authorization (sign) flow. Requires the ČSOB
credential headers. The mandate body is forwarded to ČSOB as raw JSON (COBS direct-debit shape).
</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.Initiate(System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Initiate a direct-debit mandate.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.Detail(System.String,System.Threading.CancellationToken)">
<summary>Get the direct-debit mandate detail.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.Status(System.String,System.Threading.CancellationToken)">
<summary>Get the direct-debit instruction status.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.Cancel(System.String,System.Threading.CancellationToken)">
<summary>Revoke a direct-debit mandate.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.StartSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Start the authorization (SCA) of a direct-debit mandate. Returns the PSU redirect details.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.SignStatus(System.String,System.String,System.Threading.CancellationToken)">
<summary>Get the current state of an authorization (sign) transaction.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.FinalizeSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Finalize an authorization (sign) transaction.</summary>
</member>
<member name="T:Csob.Controllers.MetaController">
<summary>Service metadata endpoints. These do not require ČSOB credentials.</summary>
</member>
<member name="M:Csob.Controllers.MetaController.Health">
<summary>Liveness probe.</summary>
</member>
<member name="M:Csob.Controllers.MetaController.Version">
<summary>Service name, version and the configured upstream endpoints (no secrets).</summary>
</member>
<member name="M:Csob.Controllers.MetaController.Status">
<summary>
Reports the non-secret configuration. This service is stateless and multi-tenant: it holds
no credentials, so there is nothing per-client to report — every secret is supplied per
request via headers.
</summary>
</member>
<member name="T:Csob.Controllers.OAuthController">
<summary>
OAuth2 Authorization Code helper for the ČSOB PSD2 PSU consent flow. Build the authorization URL,
redirect the PSU to it, then exchange the returned code for tokens. The token/refresh calls run
over mutual TLS, so they require the <c>X-CSOB-Certificate</c> header; client id/secret are also
supplied per request (this service is multi-tenant).
</summary>
</member>
<member name="M:Csob.Controllers.OAuthController.AuthorizationUrl(System.String,System.String,System.String)">
<summary>Build the ČSOB authorization URL to which the PSU must be redirected.</summary>
</member>
<member name="M:Csob.Controllers.OAuthController.Token(Csob.Models.TokenExchangeRequest,System.Threading.CancellationToken)">
<summary>Exchange an authorization code for an access/refresh token (mutual TLS).</summary>
</member>
<member name="M:Csob.Controllers.OAuthController.Refresh(Csob.Models.TokenRefreshRequest,System.Threading.CancellationToken)">
<summary>Refresh an access token using a refresh token (mutual TLS).</summary>
</member>
<member name="T:Csob.Controllers.PaymentsController">
<summary>
PISP single payment initiation and its authorization (sign) flow. Requires the ČSOB credential
headers. After <c>POST /payments</c> the response carries <c>signInfo.signId</c>; use it with the
sign endpoints to drive Strong Customer Authentication (SCA).
</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.Initiate(Csob.Models.PaymentInitiationRequest,System.Threading.CancellationToken)">
<summary>Initiate a domestic (DMCT) or SEPA (ESCT) payment.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.Detail(System.String,System.Threading.CancellationToken)">
<summary>Get the full payment detail.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.Status(System.String,System.Threading.CancellationToken)">
<summary>Get the payment instruction status.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.Cancel(System.String,System.Threading.CancellationToken)">
<summary>Cancel a not-yet-authorized payment.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.StartSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Start the authorization (SCA) of a payment. Returns the PSU redirect details.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.SignStatus(System.String,System.String,System.Threading.CancellationToken)">
<summary>Get the current state of an authorization (sign) transaction.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.FinalizeSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Finalize an authorization (sign) transaction.</summary>
</member>
<member name="T:Csob.Controllers.StandingOrdersController">
<summary>PISP standing-order initiation and its authorization (sign) flow. Requires the ČSOB credential headers.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.Initiate(Csob.Models.StandingOrderInitiationRequest,System.Threading.CancellationToken)">
<summary>Initiate a standing order.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.Detail(System.String,System.Threading.CancellationToken)">
<summary>Get the standing-order detail.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.Status(System.String,System.Threading.CancellationToken)">
<summary>Get the standing-order instruction status.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.Cancel(System.String,System.Threading.CancellationToken)">
<summary>Cancel a standing order.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.StartSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Start the authorization (SCA) of a standing order. Returns the PSU redirect details.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.SignStatus(System.String,System.String,System.Threading.CancellationToken)">
<summary>Get the current state of an authorization (sign) transaction.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.FinalizeSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Finalize an authorization (sign) transaction.</summary>
</member>
<member name="T:Csob.Credentials.CredentialConstants">
<summary>
Names of the HTTP headers that carry per-request ČSOB credentials and context.
This service is multi-tenant: it stores no credentials itself. Every sensitive value is
supplied per request in a header (never the query string or body) and is forwarded to ČSOB.
Headers must therefore only be sent over TLS. Nothing here is logged or persisted.
</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.CertificateHeader">
<summary>
eIDAS client certificate (QWAC) as a Base64-encoded PKCS#12 / PFX bundle, including the
private key and the full chain. Used to establish the mutual-TLS connection to ČSOB.
Analogous to Node's <c>https.Agent({ pfx, passphrase })</c>.
</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.CertificatePasswordHeader">
<summary>Optional passphrase protecting the PFX in <see cref="F:Csob.Credentials.CredentialConstants.CertificateHeader"/>.</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.AccessTokenHeader">
<summary>OAuth2 Bearer access token obtained for the PSU; forwarded as <c>Authorization: Bearer</c>.</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.ApiKeyHeader">
<summary>ČSOB application API key; forwarded as the <c>APIKEY</c> header.</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.TppNameHeader">
<summary>TPP (third-party provider) organisation name; forwarded as the <c>TPP-Name</c> header.</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.ClientIdHeader">
<summary>OAuth2 client id of the registered TPP application (used by the OAuth helper endpoints).</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.ClientSecretHeader">
<summary>OAuth2 client secret of the registered TPP application (used by the OAuth helper endpoints).</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.UserInvolvedHeader">
<summary>Whether the PSU is online/involved in the request; forwarded as <c>User-Involved</c> (default false).</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.UserIpAddressHeader">
<summary>PSU IP address; forwarded as <c>User-IP-Address</c>.</summary>
</member>
<member name="T:Csob.Credentials.CsobCredentials">
<summary>
Fully resolved set of per-request credentials and PSU context used to call the ČSOB PSD2 API.
Built from request headers by <see cref="T:Csob.Credentials.RequestCredentialsProvider"/>; never logged.
</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.AccessToken">
<summary>OAuth2 Bearer access token (forwarded as <c>Authorization: Bearer</c>).</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.ApiKey">
<summary>ČSOB application API key (forwarded as <c>APIKEY</c>).</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.TppName">
<summary>TPP organisation name (forwarded as <c>TPP-Name</c>).</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.Certificate">
<summary>
eIDAS client certificate (with private key) for mutual TLS. Optional at the type level so
metadata endpoints can resolve context, but required for any real upstream call.
</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.UserInvolved">
<summary>Whether the PSU is online for this request (<c>User-Involved</c>); defaults to false.</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.UserIpAddress">
<summary>Optional PSU IP address (<c>User-IP-Address</c>).</summary>
</member>
<member name="T:Csob.Credentials.MissingCredentialsException">
<summary>
Raised when a request does not provide the credential headers required to call ČSOB.
Translated to HTTP 401 by the exception-handling middleware.
</summary>
</member>
<member name="T:Csob.Credentials.RequestCredentialsProvider">
<summary>
Resolves the ČSOB credentials and PSU context for the current request, exclusively from HTTP
headers (this service stores no secrets). Missing required headers produce a 401; a malformed
certificate or wrong passphrase produces a 400 (via <see cref="T:System.Security.Cryptography.CryptographicException"/>).
</summary>
</member>
<member name="M:Csob.Credentials.RequestCredentialsProvider.Header(System.String)">
<summary>Reads a single request header, returning null when absent or blank.</summary>
</member>
<member name="M:Csob.Credentials.RequestCredentialsProvider.TryBuildCertificate">
<summary>
Builds the eIDAS client certificate from the Base64 PFX header (+ optional passphrase).
Returns null when no certificate header is present.
</summary>
<exception cref="T:System.Security.Cryptography.CryptographicException">The header is not valid Base64 or the PFX/passphrase is invalid.</exception>
</member>
<member name="M:Csob.Credentials.RequestCredentialsProvider.Resolve">
<summary>
Resolves the full credential set required for an AISP/PISP/consent call. Throws
<see cref="T:Csob.Credentials.MissingCredentialsException"/> if any required header is absent.
</summary>
</member>
<member name="T:Csob.Infrastructure.CredentialHeadersOperationFilter">
<summary>
Documents the per-request credential headers in Swagger. Metadata endpoints need none; the
OAuth helper needs the certificate + client id/secret; every other (AISP/PISP/consent) endpoint
needs the certificate + access token + API key + TPP name. Headers are marked optional at the
schema level (the service validates them at runtime) but the descriptions state what is required.
</summary>
</member>
<member name="T:Csob.Infrastructure.ExceptionHandlingMiddleware">
<summary>
Translates domain exceptions into JSON <see cref="T:Microsoft.AspNetCore.Mvc.ProblemDetails"/> responses. Credentials are
never logged — only upstream status codes and error codes (the upstream's own payload). Errors
are always logged (no silent failures).
</summary>
</member>
<member name="T:Csob.Models.Amount">
<summary>Monetary amount with ISO 4217 currency.</summary>
</member>
<member name="T:Csob.Models.AmountContainer">
<summary>Wrapper matching the <c>amount</c> object whose <c>instructedAmount</c> holds the value/currency.</summary>
</member>
<member name="T:Csob.Models.AccountIdentification">
<summary>Account number identification. For domestic/SEPA writes only <c>iban</c> is required.</summary>
</member>
<member name="T:Csob.Models.AccountReference">
<summary>Account reference with optional currency (<c>debtorAccount</c>/<c>creditorAccount</c>).</summary>
</member>
<member name="P:Csob.Models.ServiceLevel.Code">
<summary>Payment scheme: DMCT (domestic), ESCT (SEPA), XBCT (cross-border), EXCT, NXCT.</summary>
</member>
<member name="P:Csob.Models.PaymentTypeInformation.InstructionPriority">
<summary>NORM (default), HIGH (express) or INST (instant).</summary>
</member>
<member name="T:Csob.Models.Party">
<summary>A party (debtor/creditor/ultimate*). Identification is variable, so kept as raw JSON.</summary>
</member>
<member name="T:Csob.Models.RemittanceInformation">
<summary>
Remittance information. <c>unstructured</c> is free text (Czech symbols may be encoded as
<c>/VS/.../SS/.../KS/...</c>); <c>structured</c> varies (its <c>reference</c> may be a string or
an array) so it is passed through as raw JSON.
</summary>
</member>
<member name="T:Csob.Models.TokenExchangeRequest">
<summary>Body for <c>POST /oauth/token</c> (Authorization Code grant). Secrets travel in headers.</summary>
</member>
<member name="P:Csob.Models.TokenExchangeRequest.Code">
<summary>Authorization code returned to the redirect URI after PSU consent.</summary>
</member>
<member name="P:Csob.Models.TokenExchangeRequest.RedirectUri">
<summary>Redirect URI registered for the TPP app; must match the one used to obtain the code.</summary>
</member>
<member name="T:Csob.Models.TokenRefreshRequest">
<summary>Body for <c>POST /oauth/refresh</c> (Refresh Token grant).</summary>
</member>
<member name="T:Csob.Models.AuthorizationUrlResponse">
<summary>Response of <c>GET /oauth/authorization-url</c>.</summary>
</member>
<member name="P:Csob.Models.AuthorizationUrlResponse.AuthorizationUrl">
<summary>Fully-built ČSOB authorization URL to which the PSU must be redirected.</summary>
</member>
<member name="P:Csob.Models.AuthorizationUrlResponse.State">
<summary>The opaque <c>state</c> value echoed back on the redirect (CSRF protection).</summary>
</member>
<member name="P:Csob.Models.PaymentIdentification.InstructionIdentification">
<summary>Unique instruction id assigned by the TPP (idempotency key). Max 35 chars.</summary>
</member>
<member name="T:Csob.Models.PaymentInitiationRequest">
<summary>
Payment initiation request (<c>POST /my/payments</c>). Covers domestic (DMCT) and SEPA (ESCT)
payments — SEPA-only fields (creditor address, agent, ultimate parties, purpose) are optional.
Any additional COBS field not modelled here is preserved via <see cref="P:Csob.Models.PaymentInitiationRequest.AdditionalData"/>.
</summary>
</member>
<member name="P:Csob.Models.PaymentInitiationRequest.AdditionalData">
<summary>Any COBS fields not explicitly modelled are forwarded to ČSOB unchanged.</summary>
</member>
<member name="P:Csob.Models.StandingOrderExecution.Interval">
<summary>DAILY, WEEKLY, MONTHLY, BI_MONTHLY, QUARTERLY, HALFYEARLY, YEARLY, SINGLE, IRREGULAR.</summary>
</member>
<member name="P:Csob.Models.StandingOrderExecution.IntervalDue">
<summary>Day within the interval (e.g. day-of-month "25").</summary>
</member>
<member name="P:Csob.Models.StandingOrderExecution.Mode">
<summary>e.g. MAX_AMOUNT_EXCEEDED, UNTIL_CANCELLATION.</summary>
</member>
<member name="P:Csob.Models.StandingOrderDetail.Exceptions">
<summary>Optional <c>exceptions</c> block (stoppages/breaks); shape varies, kept as raw JSON.</summary>
</member>
<member name="P:Csob.Models.StandingOrderDetail.Validity">
<summary>Optional <c>validity</c> block (lastExecutionDate/maxAmount); kept as raw JSON.</summary>
</member>
<member name="T:Csob.Models.StandingOrderInitiationRequest">
<summary>Standing-order initiation request (<c>POST /my/standingorders</c>).</summary>
</member>
<member name="T:Csob.Services.AccountsService">
<summary>AISP account information (accounts, balance, transactions, standing orders, direct debits).</summary>
</member>
<member name="T:Csob.Services.TransactionQuery">
<summary>Optional filters for the transactions listing (forwarded as query parameters).</summary>
</member>
<member name="P:Csob.Services.TransactionQuery.DateFrom">
<summary>ISO date (YYYY-MM-DD) lower bound.</summary>
</member>
<member name="P:Csob.Services.TransactionQuery.DateTo">
<summary>ISO date (YYYY-MM-DD) upper bound.</summary>
</member>
<member name="T:Csob.Services.ConsentsService">
<summary>
Common PSU consent lifecycle (create / detail / revoke). The consent request body varies
across COBS profiles, so it is accepted and forwarded as raw JSON.
</summary>
</member>
<member name="T:Csob.Services.DirectDebitsService">
<summary>
PISP direct-debit mandate initiation, detail/status, revocation and the sign (SCA) flow.
The mandate creation body varies across COBS profiles, so it is accepted and forwarded as raw JSON.
</summary>
</member>
<member name="T:Csob.Services.OAuthService">
<summary>
OAuth2 Authorization Code helper for the ČSOB PSD2 flow. Builds the PSU authorization URL and
exchanges/refreshes tokens against the ČSOB token endpoint. The token endpoint is behind mutual
TLS, so the client certificate header is required for the token/refresh calls. Client id/secret
are taken from per-request headers (this service is multi-tenant and stores no app credentials).
</summary>
</member>
<member name="M:Csob.Services.OAuthService.BuildAuthorizationUrl(System.String,System.String,System.String)">
<summary>Builds the authorization URL to which the PSU must be redirected.</summary>
</member>
<member name="T:Csob.Services.PaymentsService">
<summary>PISP single payment initiation, status/detail, cancellation and the sign (SCA) flow.</summary>
</member>
<member name="M:Csob.Services.PaymentsService.StartSignAsync(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Starts transaction authorization (SCA). Returns the redirect details for the PSU.</summary>
</member>
<member name="M:Csob.Services.PaymentsService.SignStatusAsync(System.String,System.String,System.Threading.CancellationToken)">
<summary>Gets the current state of an authorization (sign) transaction.</summary>
</member>
<member name="M:Csob.Services.PaymentsService.FinalizeSignAsync(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Finalizes an authorization (sign) transaction.</summary>
</member>
<member name="T:Csob.Services.StandingOrdersService">
<summary>PISP standing-order initiation, detail/status, cancellation and the sign (SCA) flow.</summary>
</member>
</members>
</doc>
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
+86
View File
@@ -0,0 +1,86 @@
# Autentizace a OAuth2 / SCA flow
ČSOB PSD2 vyžaduje tři vrstvy:
1. **Mutual TLS** s eIDAS QWAC certifikátem (na transportní vrstvě).
2. **APIKEY** identifikující registrovanou TPP aplikaci.
3. **OAuth2 Bearer access token** prokazující souhlas (consent) PSU získaný Authorization Code flow.
Tato služba je multi-tenant a **neukládá nic** všechny tři vrstvy dostává per-request v hlavičkách.
## Credential hlavičky
| Hlavička | Povinná pro | Předáno do ČSOB jako |
|---|---|---|
| `X-CSOB-Certificate` (Base64 PFX) | vše (mTLS) | TLS klientský certifikát |
| `X-CSOB-Certificate-Password` | volitelné | |
| `X-Access-Token` | AISP/PISP/consents | `Authorization: Bearer` |
| `X-API-Key` | AISP/PISP/consents | `APIKEY` |
| `X-TPP-Name` | AISP/PISP/consents | `TPP-Name` |
| `X-CSOB-Client-Id` | `/oauth/*` | OAuth `client_id` |
| `X-CSOB-Client-Secret` | `/oauth/token`, `/oauth/refresh` | OAuth `client_secret` |
`X-Request-ID` (UUID) a `Date` (RFC 7231) doplňuje služba sama.
### Příprava Base64 PFX
```bash
base64 -w0 client_qwac.pfx # Linux/macOS -> hodnota X-CSOB-Certificate
certutil -encode client_qwac.pfx out.txt # Windows (odstranit hlavičky/řádky)
```
Heslo k PFX jde do `X-CSOB-Certificate-Password`. Hodnoty posílej **výhradně přes HTTPS**.
## OAuth2 Authorization Code flow
```
1. GET /oauth/authorization-url?redirect_uri=...&scope=...&state=...
hlavičky: X-CSOB-Client-Id
-> { "authorizationUrl": "...", "state": "..." }
2. Klient přesměruje PSU na authorizationUrl. PSU se přihlásí a udělí souhlas v ČSOB.
ČSOB přesměruje zpět na redirect_uri s parametrem ?code=...&state=...
3. POST /oauth/token { "code": "...", "redirectUri": "..." }
hlavičky: X-CSOB-Certificate (+password), X-CSOB-Client-Id, X-CSOB-Client-Secret
-> { "access_token": "...", "refresh_token": "...", "expires_in": ... }
4. AISP/PISP volání s hlavičkami:
X-CSOB-Certificate, X-Access-Token, X-API-Key, X-TPP-Name
5. Obnova: POST /oauth/refresh { "refreshToken": "..." } (stejné hlavičky jako krok 3)
```
### Scopes (COBS)
`aisp.accounts`, `aisp.balances`, `aisp.transactions`, `aisp.standingorders`, `aisp.directdebits`,
`aisp.notifications`, `pisp.payments`, `pisp.standingorders`, `pisp.directdebits`, `pisp.accounts`.
## PISP autorizace platby (SCA / sign flow)
```
1. POST /payments (body s platbou)
-> odpověď obsahuje signInfo.signId a instructionStatus
2. POST /payments/{id}/sign/{signId}
-> autorizační detaily pro PSU (např. authorizationType, href.url pro redirect SCA)
3. (volitelně) GET /payments/{id}/sign/{signId} stav autorizace
(volitelně) PUT /payments/{id}/sign/{signId} finalizace
4. GET /payments/{id}/status konečný stav (ACTC, ACSC, RJCT, …)
```
Stejný sign flow platí pro `/standing-orders` a `/direct-debits`.
## Chybové odpovědi
Služba vrací `application/problem+json`:
- `401` chybí povinná credential hlavička (`missingHeaders`).
- `400` neplatný Base64 PFX nebo špatné heslo certifikátu.
- upstream status (4xx/5xx) chyba z ČSOB; pole `csobErrorCodes` a `upstreamBody` nesou originální payload.
- `502` ČSOB nedostupné nebo selhal mTLS handshake (např. neplatný/expirovaný certifikát).
- `504` timeout.
Secrets se nikdy nelogují ani nevracejí.
+54
View File
@@ -0,0 +1,54 @@
# ČSOB PSD2 služba přehled
Tato služba je tenký, **bezstavový multi-tenant proxy** mezi klientskými aplikacemi a produkčním
**ČSOB PSD2 (Open Banking) API** (Czech Open Banking Standard COBS). Vystavuje vlastní REST endpointy
a každý request 1:1 přeloží na odpovídající ČSOB volání, přičemž doplní povinné COBS hlavičky a naváže
mutual TLS pomocí certifikátu předaného v hlavičce.
## Klíčové principy
- **Žádné uložené secrets.** Certifikát, OAuth client id/secret, access token, APIKEY i TPP name přicházejí
per-request v hlavičkách (viz [authentication.md](authentication.md)). V env jsou jen ne-secret URL a metadata.
- **Věrný průchod dat.** Čtecí (GET) odpovědi a méně stabilní těla (consent, direct-debit, sign) se předávají
jako surové JSON (`JsonNode`), takže se neztrácí žádné pole. Typovaná těla mají jen platba a trvalý příkaz
(pro kvalitní Swagger), s `additionalData` pro forward-kompatibilitu.
- **mTLS per-request.** Certifikát z `X-CSOB-Certificate` (Base64 PFX) se načte do paměti (nikdy na disk)
a použije pro TLS handshake. `HttpClient` se cachuje podle thumbprintu certifikátu kvůli znovupoužití spojení.
- **Reverse proxy.** OpenAPI `servers` se nastavuje z `ROOT_PATH`, takže Swagger „Try it out“ volá přes
`/apps/csob/...` (Caddy `handle_path` prefix předtím odstraní).
## Mapování na ČSOB
| Tato služba | ČSOB PSD2 (relativně k `CSOB_API_BASE_URL`) |
|---|---|
| `GET /accounts` | `GET /my/accounts` |
| `GET /accounts/{id}/balance` | `GET /my/accounts/{id}/balance` |
| `GET /accounts/{id}/transactions` | `GET /my/accounts/{id}/transactions` |
| `GET /accounts/{id}/transactions/awaiting` | `GET /my/accounts/{id}/transactions/awaiting` |
| `GET /accounts/{id}/standing-orders` | `GET /my/accounts/{id}/standingorders` |
| `GET /accounts/{id}/direct-debits` | `GET /my/accounts/{id}/directdebits` |
| `POST /payments` | `POST /my/payments` |
| `GET /payments/{id}` / `/status` | `GET /my/payments/{id}` / `/status` |
| `POST|GET|PUT /payments/{id}/sign/{signId}` | `…/my/payments/{id}/sign/{signId}` |
| `…/standing-orders…` | `…/my/standingorders…` |
| `…/direct-debits…` | `…/my/directdebits…` |
| `POST|GET|DELETE /consents…` | `…/consents…` |
Cesty jsou centralizované v `Client/CsobApiPaths.cs`.
## Co je vědomě mimo rozsah (zatím nenapojeno)
V souladu s philosophy sesterské služby `idoklad` (dokumentovat, co není napojeno):
- **Batch payments** (`/batchpayments`) a **co-signing list** (`/authorizations`) z COBS PISP.
- **PIISP** balance check (`/balanceCheck`).
- **Šifrování hlavičkových credentials** (jako v microsoft-365-service) odloženo, spoléháme na TLS.
## Ověření a rizika
- Build je čistý (0 warnings). Lokálně ověřeno: `/health`, `/docs`, OpenAPI `servers`=`/apps/csob`,
401 bez credentials, 400 při neplatném certifikátu, dokumentace credential hlaviček per operace.
- **Reálné ČSOB volání** vyžaduje platnou PSD2 licenci + eIDAS QWAC certifikát, takže ho nelze otestovat bez
produkčních údajů klienta.
- **OAuth authorize/token URL** a přesný tvar `sign` sub-cest pocházejí z výzkumu COBS + ČSOB portálu;
URL jsou env-konfigurovatelné a cesty centralizované při odchylce ČSOB portálu je oprava jednoho místa.
+84
View File
@@ -0,0 +1,84 @@
{
"format": 1,
"restore": {
"d:\\GitHubRepository\\Hracicky\\x\\csob\\Csob.csproj": {}
},
"projects": {
"d:\\GitHubRepository\\Hracicky\\x\\csob\\Csob.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "d:\\GitHubRepository\\Hracicky\\x\\csob\\Csob.csproj",
"projectName": "Csob",
"projectPath": "d:\\GitHubRepository\\Hracicky\\x\\csob\\Csob.csproj",
"packagesPath": "C:\\Users\\GamingPC\\.nuget\\packages\\",
"outputPath": "d:\\GitHubRepository\\Hracicky\\x\\csob\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\GamingPC\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net8.0"
],
"sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"C:\\Program Files\\dotnet\\library-packs": {},
"D:\\CustomNuGetPackages": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "10.0.200"
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"dependencies": {
"Swashbuckle.AspNetCore": {
"target": "Package",
"version": "[6.9.0, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.AspNetCore.App": {
"privateAssets": "none"
},
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.204/PortableRuntimeIdentifierGraph.json"
}
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\GamingPC\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\GamingPC\.nuget\packages\" />
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server\6.0.5\build\Microsoft.Extensions.ApiDescription.Server.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server\6.0.5\build\Microsoft.Extensions.ApiDescription.Server.props')" />
<Import Project="$(NuGetPackageRoot)swashbuckle.aspnetcore\6.9.0\build\Swashbuckle.AspNetCore.props" Condition="Exists('$(NuGetPackageRoot)swashbuckle.aspnetcore\6.9.0\build\Swashbuckle.AspNetCore.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">C:\Users\GamingPC\.nuget\packages\microsoft.extensions.apidescription.server\6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server>
</PropertyGroup>
</Project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server\6.0.5\build\Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server\6.0.5\build\Microsoft.Extensions.ApiDescription.Server.targets')" />
</ImportGroup>
</Project>
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
+22
View File
@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Csob")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f44ffae9fc38f3033750d626a06752e62097876f")]
[assembly: System.Reflection.AssemblyProductAttribute("Csob")]
[assembly: System.Reflection.AssemblyTitleAttribute("Csob")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
@@ -0,0 +1 @@
0991943a86580b954544318f8a8536f244a189324540e319d6b1d62a8de99070
@@ -0,0 +1,23 @@
is_global = true
build_property.TargetFramework = net8.0
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v8.0
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb = true
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = Csob
build_property.RootNamespace = Csob
build_property.ProjectDir = d:\GitHubRepository\Hracicky\x\csob\
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.RazorLangVersion = 8.0
build_property.SupportLocalizedComponentNames =
build_property.GenerateRazorMetadataSourceChecksumAttributes =
build_property.MSBuildProjectDirectory = d:\GitHubRepository\Hracicky\x\csob
build_property._RazorSourceGeneratorDebug =
build_property.EffectiveAnalysisLevelStyle = 8.0
build_property.EnableCodeStyleSeverity =
+17
View File
@@ -0,0 +1,17 @@
// <auto-generated/>
global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Routing;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Net.Http;
global using System.Net.Http.Json;
global using System.Threading;
global using System.Threading.Tasks;
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
+22
View File
@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Csob")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f44ffae9fc38f3033750d626a06752e62097876f")]
[assembly: System.Reflection.AssemblyProductAttribute("Csob")]
[assembly: System.Reflection.AssemblyTitleAttribute("Csob")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
@@ -0,0 +1 @@
60c4996bbd19af9863eeaf63653e6911961f28af17e965a302a0cd7e40f09823
@@ -0,0 +1,23 @@
is_global = true
build_property.TargetFramework = net8.0
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v8.0
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb = true
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = Csob
build_property.RootNamespace = Csob
build_property.ProjectDir = d:\GitHubRepository\Hracicky\x\csob\
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.RazorLangVersion = 8.0
build_property.SupportLocalizedComponentNames =
build_property.GenerateRazorMetadataSourceChecksumAttributes =
build_property.MSBuildProjectDirectory = d:\GitHubRepository\Hracicky\x\csob
build_property._RazorSourceGeneratorDebug =
build_property.EffectiveAnalysisLevelStyle = 8.0
build_property.EnableCodeStyleSeverity =
+17
View File
@@ -0,0 +1,17 @@
// <auto-generated/>
global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Routing;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Net.Http;
global using System.Net.Http.Json;
global using System.Threading;
global using System.Threading.Tasks;
@@ -0,0 +1,16 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute("Swashbuckle.AspNetCore.SwaggerGen")]
// Generated by the MSBuild WriteCodeFragment class.
Binary file not shown.
@@ -0,0 +1 @@
4fe5ffce92f1961f66e6e5263d5719ffa1de25f67d0273a38e9aa8b4e6cbe7ab
@@ -0,0 +1,36 @@
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\appsettings.json
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Csob.staticwebassets.endpoints.json
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Csob.exe
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Csob.deps.json
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Csob.runtimeconfig.json
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Csob.dll
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Csob.pdb
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Csob.xml
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Microsoft.OpenApi.dll
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Swashbuckle.AspNetCore.Swagger.dll
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Swashbuckle.AspNetCore.SwaggerGen.dll
d:\GitHubRepository\Hracicky\x\csob\bin\Release\net8.0\Swashbuckle.AspNetCore.SwaggerUI.dll
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.csproj.AssemblyReference.cache
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\rpswa.dswa.cache.json
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.GeneratedMSBuildEditorConfig.editorconfig
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.AssemblyInfoInputs.cache
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.AssemblyInfo.cs
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.csproj.CoreCompileInputs.cache
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.MvcApplicationPartsAssemblyInfo.cs
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.MvcApplicationPartsAssemblyInfo.cache
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\rjimswa.dswa.cache.json
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\rjsmrazor.dswa.cache.json
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\rjsmcshtml.dswa.cache.json
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\scopedcss\bundle\Csob.styles.css
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\staticwebassets.build.json
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\staticwebassets.build.json.cache
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\staticwebassets.development.json
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\staticwebassets.build.endpoints.json
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\swae.build.ex.cache
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.csproj.Up2Date
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.dll
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\refint\Csob.dll
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.xml
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.pdb
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\Csob.genruntimeconfig.cache
d:\GitHubRepository\Hracicky\x\csob\obj\Release\net8.0\ref\Csob.dll
Binary file not shown.
@@ -0,0 +1 @@
aa0d3e069791e9af17dc7d6aa9ccb6ac770b84a356fe5664aef85ad9983cc8d5
Binary file not shown.
+502
View File
@@ -0,0 +1,502 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>Csob</name>
</assembly>
<members>
<member name="T:Csob.Client.CsobApiAccessor">
<summary>
Scoped accessor that resolves the credentials for the current request and lazily builds a
single <see cref="T:Csob.Client.CsobApiClient"/> (bound to the request's mutual-TLS client) shared by all
services handling that request.
</summary>
</member>
<member name="T:Csob.Client.CsobApiClient">
<summary>
Thin HTTP wrapper around the ČSOB PSD2 resource API for a single request. It attaches the
mandatory COBS headers (<c>Authorization</c>, <c>APIKEY</c>, <c>TPP-Name</c>, <c>X-Request-ID</c>,
<c>Date</c>, <c>User-Involved</c>), sends the call over the per-request mutual-TLS client and
returns the response JSON verbatim (<see cref="T:System.Text.Json.Nodes.JsonNode"/>) so no field is lost in translation.
Non-success responses become a <see cref="T:Csob.Client.CsobApiException"/>.
</summary>
</member>
<member name="F:Csob.Client.CsobApiClient.JsonOptions">
<summary>Web defaults (camelCase) match the COBS JSON contract; null properties are omitted on write.</summary>
</member>
<member name="M:Csob.Client.CsobApiClient.ParseErrorCodes(System.String)">
<summary>Best-effort parse of the COBS error shape <c>{ "errors": [ { "error": "CODE" } ] }</c>.</summary>
</member>
<member name="T:Csob.Client.CsobApiException">
<summary>
Raised when the ČSOB PSD2 API returns a non-success HTTP status. Carries the upstream status
and the raw error body so the middleware can surface it without leaking credentials.
</summary>
</member>
<member name="P:Csob.Client.CsobApiException.ErrorCodes">
<summary>Machine-readable error codes parsed from the ČSOB <c>errors[].error</c> array (best effort).</summary>
</member>
<member name="P:Csob.Client.CsobApiException.RawBody">
<summary>Raw response body (already credential-free — it is the upstream's own error payload).</summary>
</member>
<member name="T:Csob.Client.CsobApiPaths">
<summary>
Central registry of ČSOB PSD2 resource path templates (relative to <c>CSOB_API_BASE_URL</c>).
Paths follow the Czech Open Banking Standard (COBS) as implemented by ČSOB. The account-scoped
AISP paths and the PISP <c>/my/payments/.../sign/{signId}</c> authorization flow are confirmed
against the ČSOB developer portal; the remaining COBS resources use the same <c>/my/</c> prefix.
Keep every path here so a portal-specific correction is a single-file change.
</summary>
</member>
<member name="T:Csob.Client.CsobHttpClientProvider">
<summary>
Provides <see cref="T:System.Net.Http.HttpClient"/> instances configured for mutual TLS with a per-request eIDAS
client certificate. ČSOB requires the client certificate at the transport layer, so a single
shared client cannot be used across tenants.
Clients are cached by certificate thumbprint and reused for connection pooling. In practice the
certificate identifies the TPP application (not the PSU), so the number of distinct certificates
is small and bounded by the set of calling tenants. Certificates and clients live in memory only
and never touch disk.
</summary>
</member>
<member name="M:Csob.Client.CsobHttpClientProvider.GetClient(System.Security.Cryptography.X509Certificates.X509Certificate2)">
<summary>
Returns a (cached) mutual-TLS <see cref="T:System.Net.Http.HttpClient"/> presenting <paramref name="certificate"/>.
A fresh certificate instance is built per request; if an equivalent one (same thumbprint) is
already cached, the redundant instance is disposed so it does not leak.
</summary>
</member>
<member name="T:Csob.Configuration.CsobSettings">
<summary>
Service configuration resolved from environment variables.
This service is a stateless, multi-tenant proxy in front of the ČSOB PSD2 (Open Banking)
API. Unlike the sibling iDoklad service, it holds <b>no per-client secrets</b>: the eIDAS
client certificate, OAuth client id/secret, access token, API key and TPP name are all
supplied per request as HTTP headers by the calling client service
(see <see cref="T:Csob.Credentials.CredentialConstants"/>). Only non-secret infrastructure
configuration (API/OAuth base URLs, app metadata, reverse-proxy prefix, timeout) lives here.
</summary>
</member>
<member name="P:Csob.Configuration.CsobSettings.RootPath">
<summary>Public reverse-proxy prefix (e.g. <c>/apps/csob</c>) injected by AppFactory.</summary>
</member>
<member name="P:Csob.Configuration.CsobSettings.ApiBaseUrl">
<summary>
Base URL of the ČSOB PSD2 resource API. Production default; override for any other
environment. All AISP/PISP/consent path templates are appended to this base.
</summary>
</member>
<member name="P:Csob.Configuration.CsobSettings.OAuthAuthorizeUrl">
<summary>
OAuth2 authorization endpoint (Authorization Code flow, PSU redirect). Production default;
verify against the current ČSOB developer portal as the host may change.
</summary>
</member>
<member name="P:Csob.Configuration.CsobSettings.OAuthTokenUrl">
<summary>
OAuth2 token endpoint (code-&gt;token and refresh). Production default; verify against the
current ČSOB developer portal.
</summary>
</member>
<member name="P:Csob.Configuration.CsobSettings.RequestTimeoutSeconds">
<summary>Upstream HTTP request timeout in seconds.</summary>
</member>
<member name="T:Csob.Controllers.AccountsController">
<summary>
AISP account information. All endpoints require the ČSOB credential headers
(eIDAS certificate, access token, API key, TPP name). See the Swagger description for details.
</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.List(System.Nullable{System.Int32},System.Nullable{System.Int32},System.String,System.String,System.Threading.CancellationToken)">
<summary>List the PSU's payment accounts (paged).</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.Balance(System.String,System.String,System.Threading.CancellationToken)">
<summary>Get the balance(s) of an account.</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.Transactions(System.String,System.Nullable{System.Int32},System.Nullable{System.Int32},System.String,System.String,System.String,System.String,System.Threading.CancellationToken)">
<summary>List booked transactions of an account (paged, optional date range).</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.Awaiting(System.String,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>List awaiting (pending) transactions of an account.</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.StandingOrders(System.String,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>List the account's existing standing orders.</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.StandingOrderDetail(System.String,System.String,System.Threading.CancellationToken)">
<summary>Get a standing-order detail for the account.</summary>
</member>
<member name="M:Csob.Controllers.AccountsController.DirectDebits(System.String,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Threading.CancellationToken)">
<summary>List the account's direct-debit mandates.</summary>
</member>
<member name="T:Csob.Controllers.ConsentsController">
<summary>
Common PSU consent lifecycle. Requires the ČSOB credential headers. The consent body is
forwarded to ČSOB as raw JSON (COBS consent shape).
</summary>
</member>
<member name="M:Csob.Controllers.ConsentsController.Create(System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Create a consent.</summary>
</member>
<member name="M:Csob.Controllers.ConsentsController.Detail(System.String,System.Threading.CancellationToken)">
<summary>Get a consent detail.</summary>
</member>
<member name="M:Csob.Controllers.ConsentsController.Delete(System.String,System.Threading.CancellationToken)">
<summary>Revoke a consent.</summary>
</member>
<member name="T:Csob.Controllers.DirectDebitsController">
<summary>
PISP direct-debit mandate initiation and its authorization (sign) flow. Requires the ČSOB
credential headers. The mandate body is forwarded to ČSOB as raw JSON (COBS direct-debit shape).
</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.Initiate(System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Initiate a direct-debit mandate.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.Detail(System.String,System.Threading.CancellationToken)">
<summary>Get the direct-debit mandate detail.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.Status(System.String,System.Threading.CancellationToken)">
<summary>Get the direct-debit instruction status.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.Cancel(System.String,System.Threading.CancellationToken)">
<summary>Revoke a direct-debit mandate.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.StartSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Start the authorization (SCA) of a direct-debit mandate. Returns the PSU redirect details.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.SignStatus(System.String,System.String,System.Threading.CancellationToken)">
<summary>Get the current state of an authorization (sign) transaction.</summary>
</member>
<member name="M:Csob.Controllers.DirectDebitsController.FinalizeSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Finalize an authorization (sign) transaction.</summary>
</member>
<member name="T:Csob.Controllers.MetaController">
<summary>Service metadata endpoints. These do not require ČSOB credentials.</summary>
</member>
<member name="M:Csob.Controllers.MetaController.Health">
<summary>Liveness probe.</summary>
</member>
<member name="M:Csob.Controllers.MetaController.Version">
<summary>Service name, version and the configured upstream endpoints (no secrets).</summary>
</member>
<member name="M:Csob.Controllers.MetaController.Status">
<summary>
Reports the non-secret configuration. This service is stateless and multi-tenant: it holds
no credentials, so there is nothing per-client to report — every secret is supplied per
request via headers.
</summary>
</member>
<member name="T:Csob.Controllers.OAuthController">
<summary>
OAuth2 Authorization Code helper for the ČSOB PSD2 PSU consent flow. Build the authorization URL,
redirect the PSU to it, then exchange the returned code for tokens. The token/refresh calls run
over mutual TLS, so they require the <c>X-CSOB-Certificate</c> header; client id/secret are also
supplied per request (this service is multi-tenant).
</summary>
</member>
<member name="M:Csob.Controllers.OAuthController.AuthorizationUrl(System.String,System.String,System.String)">
<summary>Build the ČSOB authorization URL to which the PSU must be redirected.</summary>
</member>
<member name="M:Csob.Controllers.OAuthController.Token(Csob.Models.TokenExchangeRequest,System.Threading.CancellationToken)">
<summary>Exchange an authorization code for an access/refresh token (mutual TLS).</summary>
</member>
<member name="M:Csob.Controllers.OAuthController.Refresh(Csob.Models.TokenRefreshRequest,System.Threading.CancellationToken)">
<summary>Refresh an access token using a refresh token (mutual TLS).</summary>
</member>
<member name="T:Csob.Controllers.PaymentsController">
<summary>
PISP single payment initiation and its authorization (sign) flow. Requires the ČSOB credential
headers. After <c>POST /payments</c> the response carries <c>signInfo.signId</c>; use it with the
sign endpoints to drive Strong Customer Authentication (SCA).
</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.Initiate(Csob.Models.PaymentInitiationRequest,System.Threading.CancellationToken)">
<summary>Initiate a domestic (DMCT) or SEPA (ESCT) payment.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.Detail(System.String,System.Threading.CancellationToken)">
<summary>Get the full payment detail.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.Status(System.String,System.Threading.CancellationToken)">
<summary>Get the payment instruction status.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.Cancel(System.String,System.Threading.CancellationToken)">
<summary>Cancel a not-yet-authorized payment.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.StartSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Start the authorization (SCA) of a payment. Returns the PSU redirect details.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.SignStatus(System.String,System.String,System.Threading.CancellationToken)">
<summary>Get the current state of an authorization (sign) transaction.</summary>
</member>
<member name="M:Csob.Controllers.PaymentsController.FinalizeSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Finalize an authorization (sign) transaction.</summary>
</member>
<member name="T:Csob.Controllers.StandingOrdersController">
<summary>PISP standing-order initiation and its authorization (sign) flow. Requires the ČSOB credential headers.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.Initiate(Csob.Models.StandingOrderInitiationRequest,System.Threading.CancellationToken)">
<summary>Initiate a standing order.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.Detail(System.String,System.Threading.CancellationToken)">
<summary>Get the standing-order detail.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.Status(System.String,System.Threading.CancellationToken)">
<summary>Get the standing-order instruction status.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.Cancel(System.String,System.Threading.CancellationToken)">
<summary>Cancel a standing order.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.StartSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Start the authorization (SCA) of a standing order. Returns the PSU redirect details.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.SignStatus(System.String,System.String,System.Threading.CancellationToken)">
<summary>Get the current state of an authorization (sign) transaction.</summary>
</member>
<member name="M:Csob.Controllers.StandingOrdersController.FinalizeSign(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Finalize an authorization (sign) transaction.</summary>
</member>
<member name="T:Csob.Credentials.CredentialConstants">
<summary>
Names of the HTTP headers that carry per-request ČSOB credentials and context.
This service is multi-tenant: it stores no credentials itself. Every sensitive value is
supplied per request in a header (never the query string or body) and is forwarded to ČSOB.
Headers must therefore only be sent over TLS. Nothing here is logged or persisted.
</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.CertificateHeader">
<summary>
eIDAS client certificate (QWAC) as a Base64-encoded PKCS#12 / PFX bundle, including the
private key and the full chain. Used to establish the mutual-TLS connection to ČSOB.
Analogous to Node's <c>https.Agent({ pfx, passphrase })</c>.
</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.CertificatePasswordHeader">
<summary>Optional passphrase protecting the PFX in <see cref="F:Csob.Credentials.CredentialConstants.CertificateHeader"/>.</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.AccessTokenHeader">
<summary>OAuth2 Bearer access token obtained for the PSU; forwarded as <c>Authorization: Bearer</c>.</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.ApiKeyHeader">
<summary>ČSOB application API key; forwarded as the <c>APIKEY</c> header.</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.TppNameHeader">
<summary>TPP (third-party provider) organisation name; forwarded as the <c>TPP-Name</c> header.</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.ClientIdHeader">
<summary>OAuth2 client id of the registered TPP application (used by the OAuth helper endpoints).</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.ClientSecretHeader">
<summary>OAuth2 client secret of the registered TPP application (used by the OAuth helper endpoints).</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.UserInvolvedHeader">
<summary>Whether the PSU is online/involved in the request; forwarded as <c>User-Involved</c> (default false).</summary>
</member>
<member name="F:Csob.Credentials.CredentialConstants.UserIpAddressHeader">
<summary>PSU IP address; forwarded as <c>User-IP-Address</c>.</summary>
</member>
<member name="T:Csob.Credentials.CsobCredentials">
<summary>
Fully resolved set of per-request credentials and PSU context used to call the ČSOB PSD2 API.
Built from request headers by <see cref="T:Csob.Credentials.RequestCredentialsProvider"/>; never logged.
</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.AccessToken">
<summary>OAuth2 Bearer access token (forwarded as <c>Authorization: Bearer</c>).</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.ApiKey">
<summary>ČSOB application API key (forwarded as <c>APIKEY</c>).</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.TppName">
<summary>TPP organisation name (forwarded as <c>TPP-Name</c>).</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.Certificate">
<summary>
eIDAS client certificate (with private key) for mutual TLS. Optional at the type level so
metadata endpoints can resolve context, but required for any real upstream call.
</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.UserInvolved">
<summary>Whether the PSU is online for this request (<c>User-Involved</c>); defaults to false.</summary>
</member>
<member name="P:Csob.Credentials.CsobCredentials.UserIpAddress">
<summary>Optional PSU IP address (<c>User-IP-Address</c>).</summary>
</member>
<member name="T:Csob.Credentials.MissingCredentialsException">
<summary>
Raised when a request does not provide the credential headers required to call ČSOB.
Translated to HTTP 401 by the exception-handling middleware.
</summary>
</member>
<member name="T:Csob.Credentials.RequestCredentialsProvider">
<summary>
Resolves the ČSOB credentials and PSU context for the current request, exclusively from HTTP
headers (this service stores no secrets). Missing required headers produce a 401; a malformed
certificate or wrong passphrase produces a 400 (via <see cref="T:System.Security.Cryptography.CryptographicException"/>).
</summary>
</member>
<member name="M:Csob.Credentials.RequestCredentialsProvider.Header(System.String)">
<summary>Reads a single request header, returning null when absent or blank.</summary>
</member>
<member name="M:Csob.Credentials.RequestCredentialsProvider.TryBuildCertificate">
<summary>
Builds the eIDAS client certificate from the Base64 PFX header (+ optional passphrase).
Returns null when no certificate header is present.
</summary>
<exception cref="T:System.Security.Cryptography.CryptographicException">The header is not valid Base64 or the PFX/passphrase is invalid.</exception>
</member>
<member name="M:Csob.Credentials.RequestCredentialsProvider.Resolve">
<summary>
Resolves the full credential set required for an AISP/PISP/consent call. Throws
<see cref="T:Csob.Credentials.MissingCredentialsException"/> if any required header is absent.
</summary>
</member>
<member name="T:Csob.Infrastructure.CredentialHeadersOperationFilter">
<summary>
Documents the per-request credential headers in Swagger. Metadata endpoints need none; the
OAuth helper needs the certificate + client id/secret; every other (AISP/PISP/consent) endpoint
needs the certificate + access token + API key + TPP name. Headers are marked optional at the
schema level (the service validates them at runtime) but the descriptions state what is required.
</summary>
</member>
<member name="T:Csob.Infrastructure.ExceptionHandlingMiddleware">
<summary>
Translates domain exceptions into JSON <see cref="T:Microsoft.AspNetCore.Mvc.ProblemDetails"/> responses. Credentials are
never logged — only upstream status codes and error codes (the upstream's own payload). Errors
are always logged (no silent failures).
</summary>
</member>
<member name="T:Csob.Models.Amount">
<summary>Monetary amount with ISO 4217 currency.</summary>
</member>
<member name="T:Csob.Models.AmountContainer">
<summary>Wrapper matching the <c>amount</c> object whose <c>instructedAmount</c> holds the value/currency.</summary>
</member>
<member name="T:Csob.Models.AccountIdentification">
<summary>Account number identification. For domestic/SEPA writes only <c>iban</c> is required.</summary>
</member>
<member name="T:Csob.Models.AccountReference">
<summary>Account reference with optional currency (<c>debtorAccount</c>/<c>creditorAccount</c>).</summary>
</member>
<member name="P:Csob.Models.ServiceLevel.Code">
<summary>Payment scheme: DMCT (domestic), ESCT (SEPA), XBCT (cross-border), EXCT, NXCT.</summary>
</member>
<member name="P:Csob.Models.PaymentTypeInformation.InstructionPriority">
<summary>NORM (default), HIGH (express) or INST (instant).</summary>
</member>
<member name="T:Csob.Models.Party">
<summary>A party (debtor/creditor/ultimate*). Identification is variable, so kept as raw JSON.</summary>
</member>
<member name="T:Csob.Models.RemittanceInformation">
<summary>
Remittance information. <c>unstructured</c> is free text (Czech symbols may be encoded as
<c>/VS/.../SS/.../KS/...</c>); <c>structured</c> varies (its <c>reference</c> may be a string or
an array) so it is passed through as raw JSON.
</summary>
</member>
<member name="T:Csob.Models.TokenExchangeRequest">
<summary>Body for <c>POST /oauth/token</c> (Authorization Code grant). Secrets travel in headers.</summary>
</member>
<member name="P:Csob.Models.TokenExchangeRequest.Code">
<summary>Authorization code returned to the redirect URI after PSU consent.</summary>
</member>
<member name="P:Csob.Models.TokenExchangeRequest.RedirectUri">
<summary>Redirect URI registered for the TPP app; must match the one used to obtain the code.</summary>
</member>
<member name="T:Csob.Models.TokenRefreshRequest">
<summary>Body for <c>POST /oauth/refresh</c> (Refresh Token grant).</summary>
</member>
<member name="T:Csob.Models.AuthorizationUrlResponse">
<summary>Response of <c>GET /oauth/authorization-url</c>.</summary>
</member>
<member name="P:Csob.Models.AuthorizationUrlResponse.AuthorizationUrl">
<summary>Fully-built ČSOB authorization URL to which the PSU must be redirected.</summary>
</member>
<member name="P:Csob.Models.AuthorizationUrlResponse.State">
<summary>The opaque <c>state</c> value echoed back on the redirect (CSRF protection).</summary>
</member>
<member name="P:Csob.Models.PaymentIdentification.InstructionIdentification">
<summary>Unique instruction id assigned by the TPP (idempotency key). Max 35 chars.</summary>
</member>
<member name="T:Csob.Models.PaymentInitiationRequest">
<summary>
Payment initiation request (<c>POST /my/payments</c>). Covers domestic (DMCT) and SEPA (ESCT)
payments — SEPA-only fields (creditor address, agent, ultimate parties, purpose) are optional.
Any additional COBS field not modelled here is preserved via <see cref="P:Csob.Models.PaymentInitiationRequest.AdditionalData"/>.
</summary>
</member>
<member name="P:Csob.Models.PaymentInitiationRequest.AdditionalData">
<summary>Any COBS fields not explicitly modelled are forwarded to ČSOB unchanged.</summary>
</member>
<member name="P:Csob.Models.StandingOrderExecution.Interval">
<summary>DAILY, WEEKLY, MONTHLY, BI_MONTHLY, QUARTERLY, HALFYEARLY, YEARLY, SINGLE, IRREGULAR.</summary>
</member>
<member name="P:Csob.Models.StandingOrderExecution.IntervalDue">
<summary>Day within the interval (e.g. day-of-month "25").</summary>
</member>
<member name="P:Csob.Models.StandingOrderExecution.Mode">
<summary>e.g. MAX_AMOUNT_EXCEEDED, UNTIL_CANCELLATION.</summary>
</member>
<member name="P:Csob.Models.StandingOrderDetail.Exceptions">
<summary>Optional <c>exceptions</c> block (stoppages/breaks); shape varies, kept as raw JSON.</summary>
</member>
<member name="P:Csob.Models.StandingOrderDetail.Validity">
<summary>Optional <c>validity</c> block (lastExecutionDate/maxAmount); kept as raw JSON.</summary>
</member>
<member name="T:Csob.Models.StandingOrderInitiationRequest">
<summary>Standing-order initiation request (<c>POST /my/standingorders</c>).</summary>
</member>
<member name="T:Csob.Services.AccountsService">
<summary>AISP account information (accounts, balance, transactions, standing orders, direct debits).</summary>
</member>
<member name="T:Csob.Services.TransactionQuery">
<summary>Optional filters for the transactions listing (forwarded as query parameters).</summary>
</member>
<member name="P:Csob.Services.TransactionQuery.DateFrom">
<summary>ISO date (YYYY-MM-DD) lower bound.</summary>
</member>
<member name="P:Csob.Services.TransactionQuery.DateTo">
<summary>ISO date (YYYY-MM-DD) upper bound.</summary>
</member>
<member name="T:Csob.Services.ConsentsService">
<summary>
Common PSU consent lifecycle (create / detail / revoke). The consent request body varies
across COBS profiles, so it is accepted and forwarded as raw JSON.
</summary>
</member>
<member name="T:Csob.Services.DirectDebitsService">
<summary>
PISP direct-debit mandate initiation, detail/status, revocation and the sign (SCA) flow.
The mandate creation body varies across COBS profiles, so it is accepted and forwarded as raw JSON.
</summary>
</member>
<member name="T:Csob.Services.OAuthService">
<summary>
OAuth2 Authorization Code helper for the ČSOB PSD2 flow. Builds the PSU authorization URL and
exchanges/refreshes tokens against the ČSOB token endpoint. The token endpoint is behind mutual
TLS, so the client certificate header is required for the token/refresh calls. Client id/secret
are taken from per-request headers (this service is multi-tenant and stores no app credentials).
</summary>
</member>
<member name="M:Csob.Services.OAuthService.BuildAuthorizationUrl(System.String,System.String,System.String)">
<summary>Builds the authorization URL to which the PSU must be redirected.</summary>
</member>
<member name="T:Csob.Services.PaymentsService">
<summary>PISP single payment initiation, status/detail, cancellation and the sign (SCA) flow.</summary>
</member>
<member name="M:Csob.Services.PaymentsService.StartSignAsync(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Starts transaction authorization (SCA). Returns the redirect details for the PSU.</summary>
</member>
<member name="M:Csob.Services.PaymentsService.SignStatusAsync(System.String,System.String,System.Threading.CancellationToken)">
<summary>Gets the current state of an authorization (sign) transaction.</summary>
</member>
<member name="M:Csob.Services.PaymentsService.FinalizeSignAsync(System.String,System.String,System.Text.Json.Nodes.JsonNode,System.Threading.CancellationToken)">
<summary>Finalizes an authorization (sign) transaction.</summary>
</member>
<member name="T:Csob.Services.StandingOrdersService">
<summary>PISP standing-order initiation, detail/status, cancellation and the sign (SCA) flow.</summary>
</member>
</members>
</doc>
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
{"GlobalPropertiesHash":"cmk/g0ZNXu9gJ27DjjLcpkDBlTy8RbJJZDNXpi5zobE=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["RIECXlOw3nHsuvqvxZXaPj/jy\u002BfI4EndOE5nviSURLE=","7d9SLaipaa/OPEXpY1q2f1TJiMYg8zqpA6Pih/R8Tcs=","lyWTB1SAn8j26WrlgTpjAfgPiRk8CHzueKPU4K7RU/w=","AqlAhVdvD3E/meEAwnjKEEXVbI0nCoFmuUVcx4RWscw=","9bwgN9Y/AEO67vqkjG6ZJRLuRMuKd2T8iH5YYodZ8Qo=","qnfluIVdGAut31To7iOrysqO/P2XiKnuymbhW00B3F4=","lF1S3jjLqtF4rW5S0esV3nAvTIcaMB/6tBlOniDiUvI=","pHVlcHSyz8AmNqJCN6sger3xiF6u4d2yhyHAaRW4PsM="],"CachedAssets":{},"CachedCopyCandidates":{}}
@@ -0,0 +1 @@
{"GlobalPropertiesHash":"g3hu5zSe6T51z8fRKoyDb/XOC4JY4b05h9YvX9zmd1I=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["RIECXlOw3nHsuvqvxZXaPj/jy\u002BfI4EndOE5nviSURLE=","7d9SLaipaa/OPEXpY1q2f1TJiMYg8zqpA6Pih/R8Tcs=","lyWTB1SAn8j26WrlgTpjAfgPiRk8CHzueKPU4K7RU/w=","AqlAhVdvD3E/meEAwnjKEEXVbI0nCoFmuUVcx4RWscw=","9bwgN9Y/AEO67vqkjG6ZJRLuRMuKd2T8iH5YYodZ8Qo=","qnfluIVdGAut31To7iOrysqO/P2XiKnuymbhW00B3F4=","lF1S3jjLqtF4rW5S0esV3nAvTIcaMB/6tBlOniDiUvI=","pHVlcHSyz8AmNqJCN6sger3xiF6u4d2yhyHAaRW4PsM="],"CachedAssets":{},"CachedCopyCandidates":{}}
+1
View File
@@ -0,0 +1 @@
{"GlobalPropertiesHash":"s18Zph11o4XKbouZq9V6XR416tqQ2cEqoV1zo+Pq7KQ=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["RIECXlOw3nHsuvqvxZXaPj/jy\u002BfI4EndOE5nviSURLE="],"CachedAssets":{},"CachedCopyCandidates":{}}
@@ -0,0 +1 @@
{"Version":1,"ManifestType":"Build","Endpoints":[]}
@@ -0,0 +1 @@
{"Version":1,"Hash":"BhivtHlvAyC9wdGc/wHwm/2EEkmCBBuuzAERe0/lTAw=","Source":"Csob","BasePath":"/","Mode":"Root","ManifestType":"Build","ReferencedProjectsConfiguration":[],"DiscoveryPatterns":[],"Assets":[],"Endpoints":[]}
@@ -0,0 +1 @@
BhivtHlvAyC9wdGc/wHwm/2EEkmCBBuuzAERe0/lTAw=
+533
View File
@@ -0,0 +1,533 @@
{
"version": 3,
"targets": {
"net8.0": {
"Microsoft.Extensions.ApiDescription.Server/6.0.5": {
"type": "package",
"build": {
"build/Microsoft.Extensions.ApiDescription.Server.props": {},
"build/Microsoft.Extensions.ApiDescription.Server.targets": {}
},
"buildMultiTargeting": {
"buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.props": {},
"buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.targets": {}
}
},
"Microsoft.OpenApi/1.6.14": {
"type": "package",
"compile": {
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
"related": ".pdb;.xml"
}
},
"runtime": {
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
"related": ".pdb;.xml"
}
}
},
"Swashbuckle.AspNetCore/6.9.0": {
"type": "package",
"dependencies": {
"Microsoft.Extensions.ApiDescription.Server": "6.0.5",
"Swashbuckle.AspNetCore.Swagger": "6.9.0",
"Swashbuckle.AspNetCore.SwaggerGen": "6.9.0",
"Swashbuckle.AspNetCore.SwaggerUI": "6.9.0"
},
"build": {
"build/Swashbuckle.AspNetCore.props": {}
},
"buildMultiTargeting": {
"buildMultiTargeting/Swashbuckle.AspNetCore.props": {}
}
},
"Swashbuckle.AspNetCore.Swagger/6.9.0": {
"type": "package",
"dependencies": {
"Microsoft.OpenApi": "1.6.14"
},
"compile": {
"lib/net8.0/Swashbuckle.AspNetCore.Swagger.dll": {
"related": ".pdb;.xml"
}
},
"runtime": {
"lib/net8.0/Swashbuckle.AspNetCore.Swagger.dll": {
"related": ".pdb;.xml"
}
},
"frameworkReferences": [
"Microsoft.AspNetCore.App"
]
},
"Swashbuckle.AspNetCore.SwaggerGen/6.9.0": {
"type": "package",
"dependencies": {
"Swashbuckle.AspNetCore.Swagger": "6.9.0"
},
"compile": {
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
"related": ".pdb;.xml"
}
},
"runtime": {
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
"related": ".pdb;.xml"
}
}
},
"Swashbuckle.AspNetCore.SwaggerUI/6.9.0": {
"type": "package",
"compile": {
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
"related": ".pdb;.xml"
}
},
"runtime": {
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
"related": ".pdb;.xml"
}
},
"frameworkReferences": [
"Microsoft.AspNetCore.App"
]
}
}
},
"libraries": {
"Microsoft.Extensions.ApiDescription.Server/6.0.5": {
"sha512": "Ckb5EDBUNJdFWyajfXzUIMRkhf52fHZOQuuZg/oiu8y7zDCVwD0iHhew6MnThjHmevanpxL3f5ci2TtHQEN6bw==",
"type": "package",
"path": "microsoft.extensions.apidescription.server/6.0.5",
"hasTools": true,
"files": [
".nupkg.metadata",
".signature.p7s",
"Icon.png",
"build/Microsoft.Extensions.ApiDescription.Server.props",
"build/Microsoft.Extensions.ApiDescription.Server.targets",
"buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.props",
"buildMultiTargeting/Microsoft.Extensions.ApiDescription.Server.targets",
"microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512",
"microsoft.extensions.apidescription.server.nuspec",
"tools/Newtonsoft.Json.dll",
"tools/dotnet-getdocument.deps.json",
"tools/dotnet-getdocument.dll",
"tools/dotnet-getdocument.runtimeconfig.json",
"tools/net461-x86/GetDocument.Insider.exe",
"tools/net461-x86/GetDocument.Insider.exe.config",
"tools/net461-x86/Microsoft.Win32.Primitives.dll",
"tools/net461-x86/System.AppContext.dll",
"tools/net461-x86/System.Buffers.dll",
"tools/net461-x86/System.Collections.Concurrent.dll",
"tools/net461-x86/System.Collections.NonGeneric.dll",
"tools/net461-x86/System.Collections.Specialized.dll",
"tools/net461-x86/System.Collections.dll",
"tools/net461-x86/System.ComponentModel.EventBasedAsync.dll",
"tools/net461-x86/System.ComponentModel.Primitives.dll",
"tools/net461-x86/System.ComponentModel.TypeConverter.dll",
"tools/net461-x86/System.ComponentModel.dll",
"tools/net461-x86/System.Console.dll",
"tools/net461-x86/System.Data.Common.dll",
"tools/net461-x86/System.Diagnostics.Contracts.dll",
"tools/net461-x86/System.Diagnostics.Debug.dll",
"tools/net461-x86/System.Diagnostics.DiagnosticSource.dll",
"tools/net461-x86/System.Diagnostics.FileVersionInfo.dll",
"tools/net461-x86/System.Diagnostics.Process.dll",
"tools/net461-x86/System.Diagnostics.StackTrace.dll",
"tools/net461-x86/System.Diagnostics.TextWriterTraceListener.dll",
"tools/net461-x86/System.Diagnostics.Tools.dll",
"tools/net461-x86/System.Diagnostics.TraceSource.dll",
"tools/net461-x86/System.Diagnostics.Tracing.dll",
"tools/net461-x86/System.Drawing.Primitives.dll",
"tools/net461-x86/System.Dynamic.Runtime.dll",
"tools/net461-x86/System.Globalization.Calendars.dll",
"tools/net461-x86/System.Globalization.Extensions.dll",
"tools/net461-x86/System.Globalization.dll",
"tools/net461-x86/System.IO.Compression.ZipFile.dll",
"tools/net461-x86/System.IO.Compression.dll",
"tools/net461-x86/System.IO.FileSystem.DriveInfo.dll",
"tools/net461-x86/System.IO.FileSystem.Primitives.dll",
"tools/net461-x86/System.IO.FileSystem.Watcher.dll",
"tools/net461-x86/System.IO.FileSystem.dll",
"tools/net461-x86/System.IO.IsolatedStorage.dll",
"tools/net461-x86/System.IO.MemoryMappedFiles.dll",
"tools/net461-x86/System.IO.Pipes.dll",
"tools/net461-x86/System.IO.UnmanagedMemoryStream.dll",
"tools/net461-x86/System.IO.dll",
"tools/net461-x86/System.Linq.Expressions.dll",
"tools/net461-x86/System.Linq.Parallel.dll",
"tools/net461-x86/System.Linq.Queryable.dll",
"tools/net461-x86/System.Linq.dll",
"tools/net461-x86/System.Memory.dll",
"tools/net461-x86/System.Net.Http.dll",
"tools/net461-x86/System.Net.NameResolution.dll",
"tools/net461-x86/System.Net.NetworkInformation.dll",
"tools/net461-x86/System.Net.Ping.dll",
"tools/net461-x86/System.Net.Primitives.dll",
"tools/net461-x86/System.Net.Requests.dll",
"tools/net461-x86/System.Net.Security.dll",
"tools/net461-x86/System.Net.Sockets.dll",
"tools/net461-x86/System.Net.WebHeaderCollection.dll",
"tools/net461-x86/System.Net.WebSockets.Client.dll",
"tools/net461-x86/System.Net.WebSockets.dll",
"tools/net461-x86/System.Numerics.Vectors.dll",
"tools/net461-x86/System.ObjectModel.dll",
"tools/net461-x86/System.Reflection.Extensions.dll",
"tools/net461-x86/System.Reflection.Primitives.dll",
"tools/net461-x86/System.Reflection.dll",
"tools/net461-x86/System.Resources.Reader.dll",
"tools/net461-x86/System.Resources.ResourceManager.dll",
"tools/net461-x86/System.Resources.Writer.dll",
"tools/net461-x86/System.Runtime.CompilerServices.Unsafe.dll",
"tools/net461-x86/System.Runtime.CompilerServices.VisualC.dll",
"tools/net461-x86/System.Runtime.Extensions.dll",
"tools/net461-x86/System.Runtime.Handles.dll",
"tools/net461-x86/System.Runtime.InteropServices.RuntimeInformation.dll",
"tools/net461-x86/System.Runtime.InteropServices.dll",
"tools/net461-x86/System.Runtime.Numerics.dll",
"tools/net461-x86/System.Runtime.Serialization.Formatters.dll",
"tools/net461-x86/System.Runtime.Serialization.Json.dll",
"tools/net461-x86/System.Runtime.Serialization.Primitives.dll",
"tools/net461-x86/System.Runtime.Serialization.Xml.dll",
"tools/net461-x86/System.Runtime.dll",
"tools/net461-x86/System.Security.Claims.dll",
"tools/net461-x86/System.Security.Cryptography.Algorithms.dll",
"tools/net461-x86/System.Security.Cryptography.Csp.dll",
"tools/net461-x86/System.Security.Cryptography.Encoding.dll",
"tools/net461-x86/System.Security.Cryptography.Primitives.dll",
"tools/net461-x86/System.Security.Cryptography.X509Certificates.dll",
"tools/net461-x86/System.Security.Principal.dll",
"tools/net461-x86/System.Security.SecureString.dll",
"tools/net461-x86/System.Text.Encoding.Extensions.dll",
"tools/net461-x86/System.Text.Encoding.dll",
"tools/net461-x86/System.Text.RegularExpressions.dll",
"tools/net461-x86/System.Threading.Overlapped.dll",
"tools/net461-x86/System.Threading.Tasks.Parallel.dll",
"tools/net461-x86/System.Threading.Tasks.dll",
"tools/net461-x86/System.Threading.Thread.dll",
"tools/net461-x86/System.Threading.ThreadPool.dll",
"tools/net461-x86/System.Threading.Timer.dll",
"tools/net461-x86/System.Threading.dll",
"tools/net461-x86/System.ValueTuple.dll",
"tools/net461-x86/System.Xml.ReaderWriter.dll",
"tools/net461-x86/System.Xml.XDocument.dll",
"tools/net461-x86/System.Xml.XPath.XDocument.dll",
"tools/net461-x86/System.Xml.XPath.dll",
"tools/net461-x86/System.Xml.XmlDocument.dll",
"tools/net461-x86/System.Xml.XmlSerializer.dll",
"tools/net461-x86/netstandard.dll",
"tools/net461/GetDocument.Insider.exe",
"tools/net461/GetDocument.Insider.exe.config",
"tools/net461/Microsoft.Win32.Primitives.dll",
"tools/net461/System.AppContext.dll",
"tools/net461/System.Buffers.dll",
"tools/net461/System.Collections.Concurrent.dll",
"tools/net461/System.Collections.NonGeneric.dll",
"tools/net461/System.Collections.Specialized.dll",
"tools/net461/System.Collections.dll",
"tools/net461/System.ComponentModel.EventBasedAsync.dll",
"tools/net461/System.ComponentModel.Primitives.dll",
"tools/net461/System.ComponentModel.TypeConverter.dll",
"tools/net461/System.ComponentModel.dll",
"tools/net461/System.Console.dll",
"tools/net461/System.Data.Common.dll",
"tools/net461/System.Diagnostics.Contracts.dll",
"tools/net461/System.Diagnostics.Debug.dll",
"tools/net461/System.Diagnostics.DiagnosticSource.dll",
"tools/net461/System.Diagnostics.FileVersionInfo.dll",
"tools/net461/System.Diagnostics.Process.dll",
"tools/net461/System.Diagnostics.StackTrace.dll",
"tools/net461/System.Diagnostics.TextWriterTraceListener.dll",
"tools/net461/System.Diagnostics.Tools.dll",
"tools/net461/System.Diagnostics.TraceSource.dll",
"tools/net461/System.Diagnostics.Tracing.dll",
"tools/net461/System.Drawing.Primitives.dll",
"tools/net461/System.Dynamic.Runtime.dll",
"tools/net461/System.Globalization.Calendars.dll",
"tools/net461/System.Globalization.Extensions.dll",
"tools/net461/System.Globalization.dll",
"tools/net461/System.IO.Compression.ZipFile.dll",
"tools/net461/System.IO.Compression.dll",
"tools/net461/System.IO.FileSystem.DriveInfo.dll",
"tools/net461/System.IO.FileSystem.Primitives.dll",
"tools/net461/System.IO.FileSystem.Watcher.dll",
"tools/net461/System.IO.FileSystem.dll",
"tools/net461/System.IO.IsolatedStorage.dll",
"tools/net461/System.IO.MemoryMappedFiles.dll",
"tools/net461/System.IO.Pipes.dll",
"tools/net461/System.IO.UnmanagedMemoryStream.dll",
"tools/net461/System.IO.dll",
"tools/net461/System.Linq.Expressions.dll",
"tools/net461/System.Linq.Parallel.dll",
"tools/net461/System.Linq.Queryable.dll",
"tools/net461/System.Linq.dll",
"tools/net461/System.Memory.dll",
"tools/net461/System.Net.Http.dll",
"tools/net461/System.Net.NameResolution.dll",
"tools/net461/System.Net.NetworkInformation.dll",
"tools/net461/System.Net.Ping.dll",
"tools/net461/System.Net.Primitives.dll",
"tools/net461/System.Net.Requests.dll",
"tools/net461/System.Net.Security.dll",
"tools/net461/System.Net.Sockets.dll",
"tools/net461/System.Net.WebHeaderCollection.dll",
"tools/net461/System.Net.WebSockets.Client.dll",
"tools/net461/System.Net.WebSockets.dll",
"tools/net461/System.Numerics.Vectors.dll",
"tools/net461/System.ObjectModel.dll",
"tools/net461/System.Reflection.Extensions.dll",
"tools/net461/System.Reflection.Primitives.dll",
"tools/net461/System.Reflection.dll",
"tools/net461/System.Resources.Reader.dll",
"tools/net461/System.Resources.ResourceManager.dll",
"tools/net461/System.Resources.Writer.dll",
"tools/net461/System.Runtime.CompilerServices.Unsafe.dll",
"tools/net461/System.Runtime.CompilerServices.VisualC.dll",
"tools/net461/System.Runtime.Extensions.dll",
"tools/net461/System.Runtime.Handles.dll",
"tools/net461/System.Runtime.InteropServices.RuntimeInformation.dll",
"tools/net461/System.Runtime.InteropServices.dll",
"tools/net461/System.Runtime.Numerics.dll",
"tools/net461/System.Runtime.Serialization.Formatters.dll",
"tools/net461/System.Runtime.Serialization.Json.dll",
"tools/net461/System.Runtime.Serialization.Primitives.dll",
"tools/net461/System.Runtime.Serialization.Xml.dll",
"tools/net461/System.Runtime.dll",
"tools/net461/System.Security.Claims.dll",
"tools/net461/System.Security.Cryptography.Algorithms.dll",
"tools/net461/System.Security.Cryptography.Csp.dll",
"tools/net461/System.Security.Cryptography.Encoding.dll",
"tools/net461/System.Security.Cryptography.Primitives.dll",
"tools/net461/System.Security.Cryptography.X509Certificates.dll",
"tools/net461/System.Security.Principal.dll",
"tools/net461/System.Security.SecureString.dll",
"tools/net461/System.Text.Encoding.Extensions.dll",
"tools/net461/System.Text.Encoding.dll",
"tools/net461/System.Text.RegularExpressions.dll",
"tools/net461/System.Threading.Overlapped.dll",
"tools/net461/System.Threading.Tasks.Parallel.dll",
"tools/net461/System.Threading.Tasks.dll",
"tools/net461/System.Threading.Thread.dll",
"tools/net461/System.Threading.ThreadPool.dll",
"tools/net461/System.Threading.Timer.dll",
"tools/net461/System.Threading.dll",
"tools/net461/System.ValueTuple.dll",
"tools/net461/System.Xml.ReaderWriter.dll",
"tools/net461/System.Xml.XDocument.dll",
"tools/net461/System.Xml.XPath.XDocument.dll",
"tools/net461/System.Xml.XPath.dll",
"tools/net461/System.Xml.XmlDocument.dll",
"tools/net461/System.Xml.XmlSerializer.dll",
"tools/net461/netstandard.dll",
"tools/netcoreapp2.1/GetDocument.Insider.deps.json",
"tools/netcoreapp2.1/GetDocument.Insider.dll",
"tools/netcoreapp2.1/GetDocument.Insider.runtimeconfig.json",
"tools/netcoreapp2.1/System.Diagnostics.DiagnosticSource.dll"
]
},
"Microsoft.OpenApi/1.6.14": {
"sha512": "tTaBT8qjk3xINfESyOPE2rIellPvB7qpVqiWiyA/lACVvz+xOGiXhFUfohcx82NLbi5avzLW0lx+s6oAqQijfw==",
"type": "package",
"path": "microsoft.openapi/1.6.14",
"files": [
".nupkg.metadata",
".signature.p7s",
"README.md",
"lib/netstandard2.0/Microsoft.OpenApi.dll",
"lib/netstandard2.0/Microsoft.OpenApi.pdb",
"lib/netstandard2.0/Microsoft.OpenApi.xml",
"microsoft.openapi.1.6.14.nupkg.sha512",
"microsoft.openapi.nuspec"
]
},
"Swashbuckle.AspNetCore/6.9.0": {
"sha512": "lvI+XHF21tkwXd2nDCLGJsdhdUYsY3Ax2fWUlvw81Oa6EedtnIAf5tThy8ZnPcz/9/TwsLgjgtX9ifOCIjbEPA==",
"type": "package",
"path": "swashbuckle.aspnetcore/6.9.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"build/Swashbuckle.AspNetCore.props",
"buildMultiTargeting/Swashbuckle.AspNetCore.props",
"swashbuckle.aspnetcore.6.9.0.nupkg.sha512",
"swashbuckle.aspnetcore.nuspec"
]
},
"Swashbuckle.AspNetCore.Swagger/6.9.0": {
"sha512": "P316kpxx5DnDvJwNWW8iTAXkh9DVenAxFGe9v4OUS0gil+vitH7F1feXhCtVeHN/616EFNTMh4pV2lcr9kkw/w==",
"type": "package",
"path": "swashbuckle.aspnetcore.swagger/6.9.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"lib/net5.0/Swashbuckle.AspNetCore.Swagger.dll",
"lib/net5.0/Swashbuckle.AspNetCore.Swagger.pdb",
"lib/net5.0/Swashbuckle.AspNetCore.Swagger.xml",
"lib/net6.0/Swashbuckle.AspNetCore.Swagger.dll",
"lib/net6.0/Swashbuckle.AspNetCore.Swagger.pdb",
"lib/net6.0/Swashbuckle.AspNetCore.Swagger.xml",
"lib/net7.0/Swashbuckle.AspNetCore.Swagger.dll",
"lib/net7.0/Swashbuckle.AspNetCore.Swagger.pdb",
"lib/net7.0/Swashbuckle.AspNetCore.Swagger.xml",
"lib/net8.0/Swashbuckle.AspNetCore.Swagger.dll",
"lib/net8.0/Swashbuckle.AspNetCore.Swagger.pdb",
"lib/net8.0/Swashbuckle.AspNetCore.Swagger.xml",
"lib/netcoreapp3.0/Swashbuckle.AspNetCore.Swagger.dll",
"lib/netcoreapp3.0/Swashbuckle.AspNetCore.Swagger.pdb",
"lib/netcoreapp3.0/Swashbuckle.AspNetCore.Swagger.xml",
"lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.dll",
"lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.pdb",
"lib/netstandard2.0/Swashbuckle.AspNetCore.Swagger.xml",
"package-readme.md",
"swashbuckle.aspnetcore.swagger.6.9.0.nupkg.sha512",
"swashbuckle.aspnetcore.swagger.nuspec"
]
},
"Swashbuckle.AspNetCore.SwaggerGen/6.9.0": {
"sha512": "FjeMR3fBzwVc5plfYjoHw9ptf8SOWMupvO9X35J5EgzT3L9dRqSxa+cBKzL8PwCyemY0xNrggQSB5+MFWx1axg==",
"type": "package",
"path": "swashbuckle.aspnetcore.swaggergen/6.9.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"lib/net5.0/Swashbuckle.AspNetCore.SwaggerGen.dll",
"lib/net5.0/Swashbuckle.AspNetCore.SwaggerGen.pdb",
"lib/net5.0/Swashbuckle.AspNetCore.SwaggerGen.xml",
"lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.dll",
"lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.pdb",
"lib/net6.0/Swashbuckle.AspNetCore.SwaggerGen.xml",
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerGen.dll",
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerGen.pdb",
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerGen.xml",
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll",
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.pdb",
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.xml",
"lib/netcoreapp3.0/Swashbuckle.AspNetCore.SwaggerGen.dll",
"lib/netcoreapp3.0/Swashbuckle.AspNetCore.SwaggerGen.pdb",
"lib/netcoreapp3.0/Swashbuckle.AspNetCore.SwaggerGen.xml",
"lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.dll",
"lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.pdb",
"lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerGen.xml",
"package-readme.md",
"swashbuckle.aspnetcore.swaggergen.6.9.0.nupkg.sha512",
"swashbuckle.aspnetcore.swaggergen.nuspec"
]
},
"Swashbuckle.AspNetCore.SwaggerUI/6.9.0": {
"sha512": "0OxlWBFLl2gUESZX/K7QCTz9KctKy0VxHTvLIBcyWGD4z/fv5MCMW02qzYGcReLJr4yBnNDRzApKtLh6oBpe9A==",
"type": "package",
"path": "swashbuckle.aspnetcore.swaggerui/6.9.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"lib/net5.0/Swashbuckle.AspNetCore.SwaggerUI.dll",
"lib/net5.0/Swashbuckle.AspNetCore.SwaggerUI.pdb",
"lib/net5.0/Swashbuckle.AspNetCore.SwaggerUI.xml",
"lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.dll",
"lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.pdb",
"lib/net6.0/Swashbuckle.AspNetCore.SwaggerUI.xml",
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerUI.dll",
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerUI.pdb",
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerUI.xml",
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll",
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.pdb",
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.xml",
"lib/netcoreapp3.0/Swashbuckle.AspNetCore.SwaggerUI.dll",
"lib/netcoreapp3.0/Swashbuckle.AspNetCore.SwaggerUI.pdb",
"lib/netcoreapp3.0/Swashbuckle.AspNetCore.SwaggerUI.xml",
"lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.dll",
"lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.pdb",
"lib/netstandard2.0/Swashbuckle.AspNetCore.SwaggerUI.xml",
"package-readme.md",
"swashbuckle.aspnetcore.swaggerui.6.9.0.nupkg.sha512",
"swashbuckle.aspnetcore.swaggerui.nuspec"
]
}
},
"projectFileDependencyGroups": {
"net8.0": [
"Swashbuckle.AspNetCore >= 6.9.0"
]
},
"packageFolders": {
"C:\\Users\\GamingPC\\.nuget\\packages\\": {},
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
},
"project": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "d:\\GitHubRepository\\Hracicky\\x\\csob\\Csob.csproj",
"projectName": "Csob",
"projectPath": "d:\\GitHubRepository\\Hracicky\\x\\csob\\Csob.csproj",
"packagesPath": "C:\\Users\\GamingPC\\.nuget\\packages\\",
"outputPath": "d:\\GitHubRepository\\Hracicky\\x\\csob\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\GamingPC\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net8.0"
],
"sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"C:\\Program Files\\dotnet\\library-packs": {},
"D:\\CustomNuGetPackages": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "10.0.200"
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"dependencies": {
"Swashbuckle.AspNetCore": {
"target": "Package",
"version": "[6.9.0, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.AspNetCore.App": {
"privateAssets": "none"
},
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.204/PortableRuntimeIdentifierGraph.json"
}
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"version": 2,
"dgSpecHash": "oxCvoNmhuPk=",
"success": true,
"projectFilePath": "d:\\GitHubRepository\\Hracicky\\x\\csob\\Csob.csproj",
"expectedPackageFiles": [
"C:\\Users\\GamingPC\\.nuget\\packages\\microsoft.extensions.apidescription.server\\6.0.5\\microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512",
"C:\\Users\\GamingPC\\.nuget\\packages\\microsoft.openapi\\1.6.14\\microsoft.openapi.1.6.14.nupkg.sha512",
"C:\\Users\\GamingPC\\.nuget\\packages\\swashbuckle.aspnetcore\\6.9.0\\swashbuckle.aspnetcore.6.9.0.nupkg.sha512",
"C:\\Users\\GamingPC\\.nuget\\packages\\swashbuckle.aspnetcore.swagger\\6.9.0\\swashbuckle.aspnetcore.swagger.6.9.0.nupkg.sha512",
"C:\\Users\\GamingPC\\.nuget\\packages\\swashbuckle.aspnetcore.swaggergen\\6.9.0\\swashbuckle.aspnetcore.swaggergen.6.9.0.nupkg.sha512",
"C:\\Users\\GamingPC\\.nuget\\packages\\swashbuckle.aspnetcore.swaggerui\\6.9.0\\swashbuckle.aspnetcore.swaggerui.6.9.0.nupkg.sha512"
],
"logs": []
}