backend phase 11

This commit is contained in:
hamid
2026-07-09 02:13:30 +03:30
parent 23605591eb
commit 465f75c29e
78 changed files with 9555 additions and 27 deletions
@@ -0,0 +1,34 @@
#nullable enable
using Baya.Application.Contracts.Payments;
using Microsoft.Extensions.Options;
namespace Baya.Infrastructure.CrossCutting.Seams;
/// <summary>
/// A thin, deterministic mock <see cref="IBnplProvider"/> so b11's <c>bnpl_revert</c> refund path is exercised
/// before b12 merges — <b>b12 owns the real seam definition and its full adapter</b> (SnappPay/Tara). Revert
/// and update both succeed, echo a deterministic <c>external_revert_reference</c> derived from the order +
/// idempotency key, and report a nullable provider commission reversal (null by default — some providers keep
/// their fee on a refund; the amount is reconciled from the response, never hardcoded).
/// </summary>
public sealed class MockBnplProvider(IOptions<SeamOptions> options) : IBnplProvider
{
private readonly BnplOptions _options = options.Value.Bnpl;
public ValueTask<BnplRevertResult> RevertAsync(string providerOrderReference, long amountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
=> Result(providerOrderReference, idempotencyKey);
public ValueTask<BnplRevertResult> UpdateAsync(string providerOrderReference, long newAmountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
=> Result(providerOrderReference, idempotencyKey);
private ValueTask<BnplRevertResult> Result(string providerOrderReference, string idempotencyKey)
{
if (_options.ForceFailure)
return ValueTask.FromResult(new BnplRevertResult(PaymentProviderStatus.Failed, null, null));
return ValueTask.FromResult(new BnplRevertResult(
PaymentProviderStatus.Succeeded,
ExternalRevertReference: $"mock-bnpl-revert-{providerOrderReference}-{idempotencyKey}",
ProviderCommissionReversedAmount: _options.ReverseProviderCommission ? 0 : null));
}
}
@@ -0,0 +1,28 @@
#nullable enable
using Baya.Application.Contracts.Invoices;
using Microsoft.Extensions.Options;
namespace Baya.Infrastructure.CrossCutting.Seams;
/// <summary>
/// Deterministic mock <see cref="IMoadianClient"/> (b11) — no external call. By default a submission leaves the
/// invoice at <c>moadian_status = pending</c> with no reference (the real reconciliation later flips it to
/// <c>registered</c>). Set <see cref="MoadianOptions.ForceRegistered"/> to have it return <c>registered</c> with
/// a deterministic fake 22-digit reference so the <c>registered</c>/reconciliation path is testable. The real
/// سامانه مودیان adapter (enrollment, the معاملات/invoice submission API, the 22-digit reference) swaps only this
/// registration.
/// </summary>
public sealed class MockMoadianClient(IOptions<SeamOptions> options) : IMoadianClient
{
private readonly MoadianOptions _options = options.Value.Moadian;
public ValueTask<MoadianSubmissionResult> SubmitAsync(InvoiceSubmission submission, CancellationToken cancellationToken = default)
{
if (!_options.ForceRegistered)
return ValueTask.FromResult(new MoadianSubmissionResult(Baya.Domain.Entities.Invoices.MoadianStatus.Pending, null));
// A deterministic 22-digit reference derived from the booking id (right-aligned, zero-padded).
var reference = submission.BookingId.ToString().PadLeft(22, '0');
return ValueTask.FromResult(new MoadianSubmissionResult(Baya.Domain.Entities.Invoices.MoadianStatus.Registered, reference));
}
}
@@ -24,6 +24,6 @@ public sealed class MockPaymentProvider : IPaymentProvider
public ValueTask<PaymentVerifyResult> VerifyAsync(string gatewayReferenceCode, long expectedAmountIrr, CancellationToken cancellationToken = default)
=> ValueTask.FromResult(new PaymentVerifyResult(PaymentProviderStatus.Succeeded, expectedAmountIrr));
public ValueTask<PaymentRefundResult> RefundAsync(string gatewayReferenceCode, long amountIrr, CancellationToken cancellationToken = default)
=> ValueTask.FromResult(new PaymentRefundResult(PaymentProviderStatus.Succeeded, $"mock-refund-{gatewayReferenceCode}"));
public ValueTask<PaymentRefundResult> RefundAsync(string gatewayReferenceCode, long amountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
=> ValueTask.FromResult(new PaymentRefundResult(PaymentProviderStatus.Succeeded, $"mock-refund-{gatewayReferenceCode}-{idempotencyKey}"));
}
@@ -16,6 +16,34 @@ public sealed class SeamOptions
public IdentityKycOptions IdentityKyc { get; set; } = new();
public PaymentCaptureOptions PaymentCapture { get; set; } = new();
public PaymentsOptions Payments { get; set; } = new();
public MoadianOptions Moadian { get; set; } = new();
public BnplOptions Bnpl { get; set; } = new();
}
/// <summary>
/// Tunes the mock <c>IMoadianClient</c> (b11 e-invoicing). By default a submission stays <c>pending</c> with no
/// reference. Set <see cref="ForceRegistered"/> to make it return <c>registered</c> with a fake 22-digit ref so
/// the reconciliation/registered path is testable. The real سامانه مودیان adapter ignores these.
/// </summary>
public sealed class MoadianOptions
{
/// <summary>When true, a submission returns <c>registered</c> + a deterministic fake 22-digit reference.</summary>
public bool ForceRegistered { get; set; }
}
/// <summary>
/// Tunes the thin local mock <c>IBnplProvider</c> b11 registers until b12 ships the real seam. By default a
/// revert/update succeeds and the provider keeps its commission (null reversal). The real b12 adapter ignores
/// these.
/// </summary>
public sealed class BnplOptions
{
/// <summary>When true, every revert/update fails so the refund-channel-refused path is testable.</summary>
public bool ForceFailure { get; set; }
/// <summary>When true, the mock reports the provider returned its commission (a non-null, zero reversal
/// placeholder) so the <c>provider_commission_reversed_amount</c> reconciliation is exercised.</summary>
public bool ReverseProviderCommission { get; set; }
}
/// <summary>
@@ -1,4 +1,5 @@
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Invoices;
using Baya.Application.Contracts.Payments;
using Baya.Infrastructure.CrossCutting.Seams;
using Microsoft.Extensions.Configuration;
@@ -58,6 +59,12 @@ public static class ServiceCollectionExtension
services.AddSingleton<IWebhookVerifier, MockWebhookVerifier>();
services.AddSingleton<IDistributedLock, InProcessDistributedLock>();
// Refunds/invoices seams (backend-phase-11). سامانه مودیان e-invoicing is mocked (pending/no-ref by
// default; config can force registered). IBnplProvider is a thin local stub so the bnpl_revert refund
// path runs before b12 merges — b12 owns the real seam. Both swap in by a registration change only.
services.AddSingleton<IMoadianClient, MockMoadianClient>();
services.AddSingleton<IBnplProvider, MockBnplProvider>();
return services;
}
}