using Baya.Application.Contracts.Common; using Baya.Infrastructure.CrossCutting.Seams; using Microsoft.Extensions.DependencyInjection; namespace Baya.Infrastructure.CrossCutting.ServiceConfiguration; public static class DevelopmentSeamExtensions { /// /// Development-only wiring for the OTP bring-up bridge. Registers and decorates /// the registered by AddCrossCuttingSeams with /// , so each OTP is also captured in memory for /// /api/v1/dev/last_otp/{phone} to serve. MUST be called only inside /// builder.Environment.IsDevelopment(), and only for a capture-safe provider (the log-only /// or the Development-only Telegram relay — Program.cs owns that /// condition): nothing here is wired in any other environment, which — together with the endpoint's own /// IsDevelopment() guard — makes the OTP echo impossible to enable outside Development. /// public static IServiceCollection AddDevelopmentOtpCapture(this IServiceCollection services) { services.AddSingleton(); // 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(sp => new DevCapturingSmsSender( ResolveSender(sp, inner), sp.GetRequiredService())); 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."), }; /// /// Development/Testing-only re-registration of the real over the /// production (refinement-phase-8, 6.4). The bookings/convert /// 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. /// public static IServiceCollection AddDevelopmentPaymentCapture(this IServiceCollection services) { services.AddSingleton(); return services; } }