59 lines
3.3 KiB
C#
59 lines
3.3 KiB
C#
using Baya.Application.Contracts.Common;
|
|
using Baya.Infrastructure.CrossCutting.Seams;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace Baya.Infrastructure.CrossCutting.ServiceConfiguration;
|
|
|
|
public static class DevelopmentSeamExtensions
|
|
{
|
|
/// <summary>
|
|
/// Development-only wiring for the OTP bring-up bridge. Registers <see cref="DevOtpStore"/> and decorates
|
|
/// the <see cref="ISmsSender"/> registered by <c>AddCrossCuttingSeams</c> with
|
|
/// <see cref="DevCapturingSmsSender"/>, so each OTP is also captured in memory for
|
|
/// <c>/api/v1/dev/last_otp/{phone}</c> to serve. MUST be called only inside
|
|
/// <c>builder.Environment.IsDevelopment()</c>, and only for a capture-safe provider (the log-only
|
|
/// <see cref="LoggingSmsSender"/> or the Development-only Telegram relay — <c>Program.cs</c> owns that
|
|
/// condition): nothing here is wired in any other environment, which — together with the endpoint's own
|
|
/// <c>IsDevelopment()</c> guard — makes the OTP echo impossible to enable outside Development.
|
|
/// </summary>
|
|
public static IServiceCollection AddDevelopmentOtpCapture(this IServiceCollection services)
|
|
{
|
|
services.AddSingleton<DevOtpStore>();
|
|
|
|
// Decorate whatever ISmsSender is already registered rather than assuming the mock — with
|
|
// Seams:Sms:Provider = telegram the inner sender is TelegramSmsSender, and re-creating a LoggingSmsSender
|
|
// here would silently swallow the delivery instead of capturing alongside it. Last registration wins for
|
|
// a single resolve, so callers transparently get the decorator and delivery behaviour is unchanged.
|
|
var inner = services.LastOrDefault(d => d.ServiceType == typeof(ISmsSender))
|
|
?? throw new InvalidOperationException(
|
|
"AddDevelopmentOtpCapture must run after AddCrossCuttingSeams — no ISmsSender is registered.");
|
|
services.Remove(inner);
|
|
|
|
services.AddSingleton<ISmsSender>(sp => new DevCapturingSmsSender(
|
|
ResolveSender(sp, inner),
|
|
sp.GetRequiredService<DevOtpStore>()));
|
|
|
|
return services;
|
|
}
|
|
|
|
private static ISmsSender ResolveSender(IServiceProvider sp, ServiceDescriptor descriptor) => descriptor switch
|
|
{
|
|
{ ImplementationInstance: ISmsSender instance } => instance,
|
|
{ ImplementationFactory: { } factory } => (ISmsSender)factory(sp),
|
|
{ ImplementationType: { } type } => (ISmsSender)ActivatorUtilities.CreateInstance(sp, type),
|
|
_ => throw new InvalidOperationException("The registered ISmsSender cannot be constructed for decoration."),
|
|
};
|
|
|
|
/// <summary>
|
|
/// Development/Testing-only re-registration of the real <see cref="MockPaymentCaptureSimulator"/> over the
|
|
/// production <see cref="DisabledPaymentCaptureSimulator"/> (refinement-phase-8, 6.4). The <c>bookings/convert</c>
|
|
/// simulator path is a dev/test affordance — production converts via the real b10 webhook confirm, not this
|
|
/// command. Last registration wins, so callers transparently get the succeeding mock in Dev/Testing.
|
|
/// </summary>
|
|
public static IServiceCollection AddDevelopmentPaymentCapture(this IServiceCollection services)
|
|
{
|
|
services.AddSingleton<IPaymentCaptureSimulator, MockPaymentCaptureSimulator>();
|
|
return services;
|
|
}
|
|
}
|