backend phase 13 & frontend phase 6
This commit is contained in:
+49
@@ -0,0 +1,49 @@
|
||||
#nullable enable
|
||||
using Baya.Application.Contracts.Payments;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Baya.Infrastructure.CrossCutting.Seams;
|
||||
|
||||
/// <summary>
|
||||
/// A deterministic, network-free mock <see cref="IBankTransferProvider" /> for the PAYA/SATNA payout rail. It
|
||||
/// moves <b>no money</b>: every instruction gets a deterministic <c>transfer_reference</c> and settles
|
||||
/// <see cref="BankTransferStatus.Paid" /> (the mock collapses the real <c>submitted → paid</c> reconciliation
|
||||
/// into one step). It <b>honours</b> the <see cref="PayoutInstruction.Method" /> chosen by the handler (PAYA vs
|
||||
/// SATNA by the config threshold) and echoes it back. A config switch forces a deterministic failure so the
|
||||
/// <c>partially_failed</c>/retry paths are testable: <see cref="BankTransferOptions.ForceFailure" /> fails every
|
||||
/// instruction (→ whole-batch failure), and <see cref="BankTransferOptions.FailIban" /> fails just that one
|
||||
/// destination (→ partial failure). A real transferor (Jibit / Vandar / Sadad payout) replaces this registration
|
||||
/// only — the source settlement account, per-nurse Sheba, and the reconciliation callback are its concern.
|
||||
/// </summary>
|
||||
public sealed class MockBankTransferProvider(IOptions<SeamOptions> options) : IBankTransferProvider
|
||||
{
|
||||
private readonly BankTransferOptions _options = options.Value.BankTransfer;
|
||||
|
||||
public ValueTask<PayoutBatchSubmitResult> SubmitPayoutBatchAsync(
|
||||
long payoutBatchId,
|
||||
IReadOnlyList<PayoutInstruction> instructions,
|
||||
string idempotencyKey,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var results = new List<PayoutInstructionResult>(instructions.Count);
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
var fail = _options.ForceFailure
|
||||
|| (!string.IsNullOrEmpty(_options.FailIban)
|
||||
&& string.Equals(instruction.Iban, _options.FailIban, StringComparison.Ordinal));
|
||||
|
||||
results.Add(fail
|
||||
? new PayoutInstructionResult(instruction.PayoutId, BankTransferStatus.Failed, null, instruction.Method, "provider_declined")
|
||||
: new PayoutInstructionResult(
|
||||
instruction.PayoutId, BankTransferStatus.Paid,
|
||||
TransferReference: $"mock-payout-{payoutBatchId}-{instruction.PayoutId}-{idempotencyKey}",
|
||||
instruction.Method, FailureReason: null));
|
||||
}
|
||||
|
||||
return ValueTask.FromResult(new PayoutBatchSubmitResult(
|
||||
ExternalBatchRef: $"mock-batch-{payoutBatchId}-{idempotencyKey}", results));
|
||||
}
|
||||
|
||||
public ValueTask<BankTransferStatus> GetPayoutStatusAsync(string externalBatchRef, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.FromResult(_options.ForceFailure ? BankTransferStatus.Failed : BankTransferStatus.Paid);
|
||||
}
|
||||
@@ -19,6 +19,24 @@ public sealed class SeamOptions
|
||||
public MoadianOptions Moadian { get; set; } = new();
|
||||
public BnplOptions Bnpl { get; set; } = new();
|
||||
public CurrencyOptions Currency { get; set; } = new();
|
||||
public BankTransferOptions BankTransfer { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tunes the mock <c>IBankTransferProvider</c> (b13 PAYA/SATNA payouts). By default every instruction settles
|
||||
/// paid with a deterministic transfer reference and no money moves. Set <see cref="ForceFailure"/> to fail the
|
||||
/// whole batch (→ <c>failed</c>) or <see cref="FailIban"/> to fail just one destination (→ <c>partially_failed</c>,
|
||||
/// so the retry path is testable). The real transferor ignores these — the source settlement account, per-nurse
|
||||
/// Sheba, and the reconciliation callback come from provider config.
|
||||
/// </summary>
|
||||
public sealed class BankTransferOptions
|
||||
{
|
||||
/// <summary>When true, every payout instruction is rejected so the whole-batch-failure path is testable.</summary>
|
||||
public bool ForceFailure { get; set; }
|
||||
|
||||
/// <summary>A designated IBAN that is rejected while others succeed — exercises the <c>partially_failed</c>
|
||||
/// batch outcome and the single-payout retry.</summary>
|
||||
public string FailIban { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+7
@@ -73,6 +73,13 @@ public static class ServiceCollectionExtension
|
||||
services.AddSingleton<IBnplProviderResolver, MockBnplProviderResolver>();
|
||||
services.AddSingleton<ICurrencyNormalizer, MockCurrencyNormalizer>();
|
||||
|
||||
// Payout bank rail (backend-phase-13). The deterministic MockBankTransferProvider settles every PAYA/SATNA
|
||||
// instruction paid with no money movement; a config switch forces whole-batch/single-row failures so the
|
||||
// partially_failed + retry paths are testable. A real transferor (Jibit/Vandar/Sadad payout) with a
|
||||
// registered source settlement account + reconciliation callback swaps in by a registration change only —
|
||||
// the payout status machine + the nurse_payout_booking_links UNIQUE remain the irreversible-transfer backstop.
|
||||
services.AddSingleton<IBankTransferProvider, MockBankTransferProvider>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user