backend phase 10
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Asp.Versioning;
|
||||
using Baya.Application.Features.Payments.Queries.GetNursePayableBalance;
|
||||
using Baya.Application.Models.Payments;
|
||||
using Baya.WebFramework.Attributes;
|
||||
using Baya.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Baya.Web.Api.Controllers.V1;
|
||||
|
||||
/// <summary>
|
||||
/// The derived per-nurse payable balance (IRR digit-string), summed from the append-only ledger — never a
|
||||
/// stored column. Authorized to the nurse themself or an admin/finance role. This is what b13 payouts read.
|
||||
/// </summary>
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/nurses")]
|
||||
[Authorize]
|
||||
[Display(Description = "Derived nurse payable balance (ledger projection)")]
|
||||
public sealed class NursePayableBalanceController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpGet("{nurseId}/payable_balance")]
|
||||
[ProducesOkApiResponseType<NursePayableBalanceDto>]
|
||||
public async Task<IActionResult> PayableBalance(long nurseId, CancellationToken cancellationToken)
|
||||
=> OperationResult(await sender.Send(new GetNursePayableBalanceQuery(nurseId), cancellationToken));
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Asp.Versioning;
|
||||
using Baya.Application.Features.Payments.Commands.InitiatePayment;
|
||||
using Baya.Application.Models.Payments;
|
||||
using Baya.WebFramework.Attributes;
|
||||
using Baya.WebFramework.BaseController;
|
||||
using Baya.WebFramework.ServiceConfiguration;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
|
||||
namespace Baya.Web.Api.Controllers.V1;
|
||||
|
||||
/// <summary>
|
||||
/// The customer-facing card-payment start. A booking exists only on capture, so a payment is initiated against
|
||||
/// the accepted <c>booking_requests</c> id; the money charged is the request's frozen gross. Rate-limited as a
|
||||
/// money endpoint and idempotency-keyed (the <c>Idempotency-Key</c> header) so a retried start reuses the same
|
||||
/// attempt. Internal account types are never exposed here — the response is just the redirect + the attempt id.
|
||||
/// </summary>
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/bookings")]
|
||||
[Authorize]
|
||||
[Display(Description = "Card payment initiation against an accepted booking request")]
|
||||
public sealed class PaymentsController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpPost("{bookingRequestId}/payments")]
|
||||
[EnableRateLimiting(RateLimitingServiceExtension.SensitivePolicy)]
|
||||
[ProducesOkApiResponseType<InitiatePaymentResult>]
|
||||
public async Task<IActionResult> Initiate(long bookingRequestId, CancellationToken cancellationToken)
|
||||
{
|
||||
var idempotencyKey = Request.Headers["Idempotency-Key"].FirstOrDefault();
|
||||
return OperationResult(await sender.Send(new InitiatePaymentCommand(bookingRequestId, idempotencyKey), cancellationToken));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Asp.Versioning;
|
||||
using Baya.Application.Features.Payments.Commands.HandlePaymentWebhook;
|
||||
using Baya.Application.Models.Payments;
|
||||
using Baya.WebFramework.Attributes;
|
||||
using Baya.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Baya.Web.Api.Controllers.V1;
|
||||
|
||||
/// <summary>
|
||||
/// The inbound PSP/BNPL callback surface. Authenticated by <b>signature</b>, not a user session, so it is
|
||||
/// anonymous to the auth pipeline; at-least-once tolerant and idempotency-deduplicated on
|
||||
/// <c>(provider, external_event_id)</c> before any money moves. The raw body is read verbatim and stored in
|
||||
/// <c>payload_json</c>.
|
||||
/// </summary>
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/webhooks")]
|
||||
[AllowAnonymous]
|
||||
[Display(Description = "PSP/BNPL payment callbacks (signature-authenticated, idempotent)")]
|
||||
public sealed class WebhooksController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpPost("payments/{provider}")]
|
||||
[ProducesOkApiResponseType<WebhookIngestResult>]
|
||||
public async Task<IActionResult> Payments(string provider, CancellationToken cancellationToken)
|
||||
{
|
||||
using var reader = new StreamReader(Request.Body, Encoding.UTF8, leaveOpen: true);
|
||||
var rawBody = await reader.ReadToEndAsync(cancellationToken);
|
||||
|
||||
var headers = Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return OperationResult(await sender.Send(new HandlePaymentWebhookCommand(provider, headers, rawBody), cancellationToken));
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,7 @@ if (!app.Environment.IsEnvironment("Testing"))
|
||||
{
|
||||
await app.ApplyMigrationsAsync();
|
||||
await app.SeedDefaultUsersAsync();
|
||||
await app.SeedPaymentGatewaysAsync();
|
||||
}
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
|
||||
Reference in New Issue
Block a user