frontend phase 5 & backend phase 12

This commit is contained in:
hamid
2026-07-09 03:05:14 +03:30
parent 465f75c29e
commit dc64472631
98 changed files with 11847 additions and 136 deletions
@@ -0,0 +1,56 @@
using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.Bnpl.Commands.RevertBnplOrder;
using Baya.Application.Features.Bnpl.Commands.SettleBnplOrder;
using Baya.Application.Features.Bnpl.Commands.VerifyBnplOrder;
using Baya.Application.Features.Bnpl.Queries.GetBnplOrderStatus;
using Baya.Application.Models.Bnpl;
using Baya.Infrastructure.Identity.Identity.PermissionManager;
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>
/// Admin-only BNPL operations console: manually drive verify/settle (also driven by the provider callback) and
/// the payout-/refund-sensitive full revert, plus read an order. The revert reverses through the provider only
/// (customer ↔ provider ↔ Balinyaar) and surfaces the async ~710-business-day customer ETA. Rate-limited as
/// money endpoints.
/// </summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/admin_bnpl")]
[Authorize(ConstantPolicies.DynamicPermission)]
[EnableRateLimiting(RateLimitingServiceExtension.SensitivePolicy)]
[Display(Description = "Admin BNPL verify/settle/revert + order status")]
public sealed class AdminBnplController(ISender sender) : BaseController
{
[HttpPost("{id}/[action]")]
[ProducesOkApiResponseType<bool>]
public async Task<IActionResult> Verify(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new VerifyBnplOrderCommand(id), cancellationToken));
[HttpPost("{id}/[action]")]
[ProducesOkApiResponseType<bool>]
public async Task<IActionResult> Settle(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new SettleBnplOrderCommand(id), cancellationToken));
[HttpPost("{id}/[action]")]
[ProducesOkApiResponseType<RevertBnplResult>]
public async Task<IActionResult> Revert(long id, RevertBnplBody body, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(
new RevertBnplOrderCommand(id, body.RefundPercentage, body.TicketId, body.ReasonNotes), cancellationToken));
[HttpGet("{id}")]
[ProducesOkApiResponseType<BnplOrderStatusDto>]
public async Task<IActionResult> Get(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new GetBnplOrderStatusQuery(id, AdminView: true), cancellationToken));
/// <summary>The revert body (the order id comes from the route). Omit <c>refund_percentage</c> for a full revert.</summary>
public record RevertBnplBody(decimal? RefundPercentage, long? TicketId, string? ReasonNotes);
}
@@ -0,0 +1,52 @@
using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.Bnpl.Commands.InitiateBnplOrder;
using Baya.Application.Features.Bnpl.Queries.CheckBnplEligibility;
using Baya.Application.Features.Bnpl.Queries.GetBnplOrderStatus;
using Baya.Application.Models.Bnpl;
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 BNPL checkout — the "pay with installments" alternative to the card path. Eligibility
/// records the plan on a <c>bnpl_transactions</c> row; initiate issues the provider token + redirect. Both are
/// tenancy-scoped to the caller and rate-limited as money endpoints. The order amount is always the request's
/// frozen gross — never client-supplied.
/// </summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/checkout_bnpl")]
[Authorize]
[EnableRateLimiting(RateLimitingServiceExtension.SensitivePolicy)]
[Display(Description = "Customer BNPL checkout (eligibility + initiate)")]
public sealed class CheckoutBnplController(ISender sender) : BaseController
{
[HttpPost("[action]")]
[ProducesOkApiResponseType<BnplEligibilityDto>]
public async Task<IActionResult> Eligibility(CheckBnplEligibilityQuery query, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(query, cancellationToken));
[HttpPost("[action]")]
[ProducesOkApiResponseType<InitiateBnplResult>]
public async Task<IActionResult> Initiate(InitiateBnplBody body, CancellationToken cancellationToken)
{
var idempotencyKey = Request.Headers["Idempotency-Key"].FirstOrDefault();
return OperationResult(await sender.Send(
new InitiateBnplOrderCommand(body.BookingRequestId, body.ProviderCode, idempotencyKey), cancellationToken));
}
[HttpGet("{id}")]
[ProducesOkApiResponseType<BnplOrderStatusDto>]
public async Task<IActionResult> Get(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new GetBnplOrderStatusQuery(id, AdminView: false), cancellationToken));
/// <summary>The initiate body (the idempotency key comes from the <c>Idempotency-Key</c> header).</summary>
public record InitiateBnplBody(long BookingRequestId, string ProviderCode);
}
@@ -0,0 +1,43 @@
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Text;
using Asp.Versioning;
using Baya.Application.Features.Bnpl.Commands.HandleBnplCallback;
using Baya.Application.Models.Bnpl;
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 inbound BNPL provider-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_code, external_event_id)</c> before any money moves. Rate-limited (per-IP). The raw body is read
/// verbatim and stored in <c>payload_json</c>.
/// </summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/webhooks_bnpl")]
[AllowAnonymous]
[EnableRateLimiting(RateLimitingServiceExtension.SensitivePolicy)]
[Display(Description = "BNPL provider callbacks (signature-authenticated, idempotent)")]
public sealed class WebhooksBnplController(ISender sender) : BaseController
{
[HttpPost("{provider}")]
[ProducesOkApiResponseType<BnplCallbackResult>]
public async Task<IActionResult> Callback(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 HandleBnplCallbackCommand(provider, headers, rawBody), cancellationToken));
}
}