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
+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));
}