Files
CMS/src/CMSMicroservice.Infrastructure/ConfigureServices.cs
T
masoodafar-web d22eb1617f
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 9m58s
feat(payment): add per-user in-memory lock for gateway operations
Introduce IUserPaymentLock to serialize payment initiate and verify flows
per user, preventing concurrent duplicate gateway requests across services.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 01:55:00 +03:30

229 lines
11 KiB
C#

using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Authorization;
using CMSMicroservice.Application.DayaLoanCQ.Services;
using CMSMicroservice.Infrastructure.Persistence;
using CMSMicroservice.Infrastructure.Persistence.Interceptors;
using CMSMicroservice.Infrastructure.BackgroundJobs;
using CMSMicroservice.Infrastructure.BackgroundServices;
using CMSMicroservice.Infrastructure.Services.Monitoring;
using CMSMicroservice.Infrastructure.Services.Authorization;
using CMSMicroservice.Infrastructure.Configuration;
using CMSMicroservice.Infrastructure.Services.Payment;
using CMSMicroservice.Infrastructure.Services.Commission;
using CMSMicroservice.Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using System.Diagnostics;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using CMSMicroservice.Infrastructure.Services;
namespace Microsoft.Extensions.DependencyInjection;
public static class ConfigureServices
{
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
{
// Configuration Settings
services.Configure<EmailSettings>(configuration.GetSection(EmailSettings.SectionName));
services.Configure<SmsSettings>(configuration.GetSection(SmsSettings.SectionName));
services.AddScoped<AuditableEntitySaveChangesInterceptor>();
services.AddScoped<HistoryTrackingSaveChangesInterceptor>();
services.AddScoped<ApplicationDbContextInitialiser>();
services.AddScoped<IGenerateJwtToken, GenerateJwtTokenService>();
services.AddScoped<IHashService, HashService>();
services.AddScoped<INetworkPlacementService, NetworkPlacementService>();
services.AddScoped<IAlertService, AlertService>();
services.AddScoped<IUserNotificationService, UserNotificationService>();
services.AddScoped<IKavenegarService, KavenegarService>();
services.AddSingleton<IUserPaymentLock, UserPaymentLockService>();
// Local file manager — files are saved to wwwroot/uploads/ on CMS disk
services.AddSingleton<CMSMicroservice.Application.Common.FileManager.IFileManager, LocalFileManager>();
services.AddScoped<IPermissionService, PermissionService>();
// Daya Loan API Service - قابل تغییر بین Mock و Real
var useMockDayaApi = configuration.GetValue<bool>("DayaApi:UseMock", false);
if (useMockDayaApi)
{
// Mock برای Development/Testing
services.AddScoped<IDayaLoanApiService, MockDayaLoanApiService>();
}
else
{
// Real Implementation با HttpClient
services.AddHttpClient<IDayaLoanApiService, DayaLoanApiService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.ConfigureHttpClient((sp, client) =>
{
var config = sp.GetRequiredService<IConfiguration>();
// Base Address
var baseAddress = config["DayaApi:BaseAddress"] ?? "https://testdaya.tadbirandishan.com";
client.BaseAddress = new Uri(baseAddress);
// Merchant Permission Key
var permissionKey = config["DayaApi:MerchantPermissionKey"];
if (!string.IsNullOrEmpty(permissionKey))
{
client.DefaultRequestHeaders.Add("merchant-permission-key", permissionKey);
}
// Timeout
client.Timeout = TimeSpan.FromSeconds(30);
});
}
// Payment Gateway Service - Multi-Provider Architecture
// پشتیبانی از درگاه‌های مختلف: ZarinPal, Daya, Mock
var paymentProvider = configuration.GetValue<string>("PaymentProvider", "Mock")?.ToLowerInvariant();
switch (paymentProvider)
{
case "zarinpal":
services.AddHttpClient<IPaymentGatewayService, ZarinPalPaymentService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
break;
case "daya":
services.AddHttpClient<IPaymentGatewayService, DayaPaymentService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
break;
case "mock":
default:
services.AddScoped<IPaymentGatewayService, MockPaymentGatewayService>();
break;
}
services.AddScoped<IApplicationDbContext>(p => p.GetRequiredService<ApplicationDbContext>());
// Week Definition Repository - کش در حافظه برای هفته‌ها (Singleton برای کش، با IServiceScopeFactory برای دسترسی به DbContext)
services.AddSingleton<WeekDefinitionRepository>();
services.AddSingleton<IWeekDefinitionRepository>(sp => sp.GetRequiredService<WeekDefinitionRepository>());
// Commission Calculation Strategy Factory - برای سوییچ بین ORM و SP
services.AddScoped<ICommissionCalculationStrategyFactory, CommissionCalculationStrategyFactory>();
// Chatika API Service - سرویس ایجاد حساب چتیکا
services.AddHttpClient<IChatikaApiService, ChatikaApiService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.ConfigureHttpClient((sp, client) =>
{
var config = sp.GetRequiredService<IConfiguration>();
client.Timeout = TimeSpan.FromSeconds(30);
});
// Background Workers - Deprecated: Using Hangfire instead
// services.AddHostedService<WeeklyNetworkCommissionWorker>();
services.AddScoped<WeeklyCommissionJob>(); // Hangfire Job (Scoped for DI)
services.AddScoped<ChatikaAccountActivationJob>(); // Hangfire Job for Chatika activation
// Expire pending discount orders after 30 minutes
services.AddHostedService<ExpirePendingOrdersService>();
// One-time: Initialize inventory records for existing products
if (configuration.GetValue<bool>("SeedWorkers:InventoryInitializer:Enabled"))
services.AddHostedService<InventoryInitializerService>();
// One-time: Seed ClubMembershipCycle for existing active memberships
if (configuration.GetValue<bool>("SeedWorkers:MagicWalletCycleSeed:Enabled"))
services.AddHostedService<MagicWalletCycleSeedService>();
// Q26: Auto-deploy stored procedures on startup (checksum-based)
services.AddHostedService<StoredProcedureDeploymentService>();
if (configuration.GetValue<bool>("UseInMemoryDatabase"))
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase("MyMemoryDb"));
}
else
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
builder => builder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
}
// Inventory Business Service
services.AddScoped<IInventoryService, InventoryService>();
#region AddAuthentication
var message = "";
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(jwtBearerOptions =>
{
//jwtBearerOptions.Authority = configuration["Authentication:Authority"];
//jwtBearerOptions.Audience = configuration["Authentication:Audience"];
//jwtBearerOptions.TokenValidationParameters.ValidateAudience = false;
//jwtBearerOptions.TokenValidationParameters.ValidateIssuer = true;
//jwtBearerOptions.TokenValidationParameters.ValidateIssuerSigningKey = false;
jwtBearerOptions.SaveToken = true;
jwtBearerOptions.RequireHttpsMetadata = false;
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = configuration["JwtIssuer"],
ValidAudience = configuration["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtSecurityKey"]))
};
try
{
jwtBearerOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = ctx =>
{
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
message += "From OnAuthenticationFailed:\n";
message += ctx.Exception.Message;
return Task.CompletedTask;
},
OnChallenge = ctx =>
{
message += "From OnChallenge:\n";
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
ctx.Response.ContentType = "text/plain";
return ctx.Response.WriteAsync(message);
},
OnMessageReceived = ctx =>
{
message = "From OnMessageReceived:\n";
ctx.Request.Headers.TryGetValue("Authorization", out var BearerToken);
if (BearerToken.Count == 0)
BearerToken = "no Bearer token sent\n";
message += "Authorization Header sent: " + BearerToken + "\n";
return Task.CompletedTask;
},
OnTokenValidated = ctx =>
{
Debug.WriteLine("token: " + ctx.SecurityToken.ToString());
return Task.CompletedTask;
}
};
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
});
services.AddAuthorization();
#endregion
return services;
}
}