From 0e9d0cad2f8f63c53048b5ac4c056d7f853b8566 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Wed, 11 Mar 2026 23:44:28 +0330 Subject: [PATCH 1/5] feat: manual payment package-based + StockMovement constraint fix - ManualPayment: add package_id to proto, handler selects package by ID - ManualPayment: remove manual amount input, use package price - ManualPayment: validator now checks PackageId instead of Amount - StockMovement: fix CHECK constraint (store negative qty for decreases) - StockMovement: Reserved/Released don't change physical Quantity - Proto NuGet bumped to 0.0.192 --- k8s/production/cms-config.yaml | 70 +++++++++++++++++-- .../CreateManualPaymentCommand.cs | 7 +- .../CreateManualPaymentCommandHandler.cs | 18 +++-- .../CreateManualPaymentCommandValidator.cs | 10 ++- .../CreateStockMovementCommandHandler.cs | 21 +++++- .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/manualpayment.proto | 3 +- 7 files changed, 113 insertions(+), 18 deletions(-) diff --git a/k8s/production/cms-config.yaml b/k8s/production/cms-config.yaml index 1dd737d..6ce28ee 100644 --- a/k8s/production/cms-config.yaml +++ b/k8s/production/cms-config.yaml @@ -19,28 +19,89 @@ type: Opaque stringData: appsettings.Production.json: | { + "PaymentProvider": "zarinpal", "ZarinPal": { "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", "UseSandbox": false }, "CmsBaseUrl": "https://cms.kbs2.ir", "FrontOfficeBaseUrl": "https://kbs2.ir", + "JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=", + "JwtIssuer": "https://localhost", + "JwtAudience": "https://localhost", + "JwtExpiryInDays": 5, "ConnectionStrings": { - "DefaultConnection": "Server=mssql-svc;Database=KBS;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;" + "DefaultConnection": "Server=mssql-svc;Database=KBS;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;", + "providerName": "System.Data.SqlClient" + }, + "Otp": { + "Secret": "K2w8k1h1mH2Qz1kqWk0c8kQ2Pq8q9H1eE2nqN1qQ8x7M=" + }, + "Monitoring": { + "SentryEnabled": false, + "SentryDsn": "", + "SlackEnabled": false, + "SlackWebhookUrl": "", + "EmailAlertsEnabled": false, + "AdminEmails": [ + "admin@example.com" + ], + "SmsNotificationsEnabled": false, + "SmsApiKey": "", + "SmsGatewayUrl": "" + }, + "Email": { + "Enabled": true, + "SmtpHost": "smtp.gmail.com", + "SmtpPort": 587, + "SmtpUsername": "your-email@gmail.com", + "SmtpPassword": "your-app-password", + "FromEmail": "noreply@foursat.com", + "FromName": "FourSat CMS", + "EnableSsl": true + }, + "Sms": { + "Enabled": true, + "Provider": "Kavenegar", + "KavenegarApiKey": "43676A4E786A6C50452F2F6252507939346A5145764B33566B456454374E657A614468706658656D6534593D", + "Sender": "1000001110100" + }, + "DayaPayment": { + "BaseUrl": "https://api.daya.ir", + "ApiKey": "YOUR_DAYA_API_KEY" + }, + "DayaApi": { + "UseMock": false, + "BaseAddress": "https://Dayadiamond.ir", + "MerchantPermissionKey": "56146364$04sXjethI5WxhItR1Q9xnmFdJzl2BB8Bclsq8dAy7YVSZp3vtt-wP7ivrcCvmKLq", + "CacheDurationMinutes": 20 + }, + "Chatika": { + "Enabled": true, + "BaseUrl": "https://api.chatika.ir", + "ApiKey": "tIukvL8dnV4cB3yVWcCD9Xyfbj8rBxm5wPt2mLyJCgTsBBoMTWjt6mFEqQwpw-er" + }, + "BackgroundJobs": { + "WeeklyCommissionCalculation": { + "Enabled": false, + "CronExpression": "5 0 * * 0" + } }, "SeedWorkers": { "MagicWalletCycleSeed": { "Enabled": true } }, - "FileStorage": { - "UploadPath": "/app/Uploads" - }, + "AllowedHosts": "*", "Kestrel": { "EndpointDefaults": { "Protocols": "Http2" } }, + "Authentication": { + "Authority": "https://ids.domain.com/", + "Audience": "domain_api" + }, "Seq": { "ServerUrl": "http://seq-svc:5341", "ApiKey": "oxpvpUzU1pZxMS4s3Fqq" @@ -52,3 +113,4 @@ stringData: } } } + diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs index 6a65a49..d350f33 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs @@ -14,10 +14,15 @@ public class CreateManualPaymentCommand : IRequest public long UserId { get; set; } /// - /// مبلغ تراکنش (تومان) + /// مبلغ تراکنش (تومان) — [DEPRECATED] مبلغ از پکیج خوانده می‌شود /// public long Amount { get; set; } + /// + /// شناسه پکیج — اگر 0 باشد، پکیج پایه استفاده می‌شود + /// + public long PackageId { get; set; } + /// /// نوع تراکنش دستی /// diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs index 41947ef..fa7fb68 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs @@ -70,10 +70,20 @@ public class CreateManualPaymentCommandHandler : IRequestHandler p.IsBasePackage && !p.IsDeleted, cancellationToken) - ?? throw new NotFoundException("پکیج پایه یافت نشد"); + // 4. بارگذاری پکیج انتخاب‌شده (یا پکیج پایه اگر PackageId=0) + Domain.Entities.Package package; + if (request.PackageId > 0) + { + package = await _context.Packages + .FirstOrDefaultAsync(p => p.Id == request.PackageId && !p.IsDeleted, cancellationToken) + ?? throw new NotFoundException($"پکیج با شناسه {request.PackageId} یافت نشد"); + } + else + { + package = await _context.Packages + .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) + ?? throw new NotFoundException("پکیج پایه یافت نشد"); + } var balanceAmount = package.Price; var discountBalanceAmount = (long)(package.Price * package.DiscountMultiplier); diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandValidator.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandValidator.cs index 9dae429..2e16891 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandValidator.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandValidator.cs @@ -11,12 +11,10 @@ public class CreateManualPaymentCommandValidator : AbstractValidator x.Amount) - .GreaterThan(0) - .WithMessage("مبلغ باید بزرگتر از صفر باشد") - .LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount) - .WithMessage("مبلغ وارد شده بیش از حد مجاز است"); + // مبلغ از پکیج خوانده می‌شود — فیلد Amount دیگر اعتبارسنجی نمی‌شود + RuleFor(x => x.PackageId) + .GreaterThanOrEqualTo(0) + .WithMessage("شناسه پکیج نامعتبر است"); RuleFor(x => x.Type) .IsInEnum() diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommandHandler.cs b/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommandHandler.cs index 8fac09f..2606931 100644 --- a/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommandHandler.cs +++ b/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommandHandler.cs @@ -61,7 +61,9 @@ public class CreateStockMovementCommandHandler : IRequestHandler -request.Quantity, + // رزرو و آزادسازی تأثیری روی Quantity فیزیکی ندارن + StockMovementType.Reserved or + StockMovementType.Released => 0, + _ => request.Quantity + }; + var entity = new StockMovement { InventoryItemId = request.InventoryItemId, MovementType = request.MovementType, - Quantity = request.Quantity, + Quantity = storedQuantity, QuantityBefore = quantityBefore, QuantityAfter = quantityAfter, Note = request.Note, diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 040b722..67fffe5 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.190 + 0.0.192 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto b/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto index edd0cab..66db59a 100644 --- a/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto +++ b/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto @@ -72,13 +72,14 @@ enum ManualPaymentStatus message CreateManualPaymentRequest { int64 user_id = 1; - int64 amount = 2; + int64 amount = 2; // [DEPRECATED] مبلغ از پکیج خوانده می‌شود — نادیده گرفته می‌شود ManualPaymentType type = 3; string description = 4; google.protobuf.StringValue reference_number = 5; google.protobuf.StringValue image_path = 6; google.protobuf.Int64Value image_document_id = 7; FileUploadModel image_file = 8; + int64 package_id = 9; // شناسه پکیج — اگر 0 باشد، پکیج پایه استفاده می‌شود } message CreateManualPaymentResponse From 370f735cf2e72de1b1b492878c74f91614d83ce1 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 12 Mar 2026 00:03:42 +0330 Subject: [PATCH 2/5] fix: bypass broken Nexus proxy, use nuget.org directly Nexus nuget-all proxy returns 500. Replace with direct nuget.org source. Keep foursat-hosted for proto push step only. --- Dockerfile | 2 +- src/NuGet.config | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 68dcb91..96b8d02 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ COPY src/NuGet.config ./ # Copy solution and project files COPY src/ ./ -# Restore with Nexus config and publish +# Restore and publish RUN dotnet restore "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" --configfile NuGet.config RUN dotnet publish "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" -c Release -o /app/publish --no-restore diff --git a/src/NuGet.config b/src/NuGet.config index 59d2375..d70eceb 100644 --- a/src/NuGet.config +++ b/src/NuGet.config @@ -2,15 +2,11 @@ - - + + - - - - From e946c56631502cf672f3693bd23293ea6e9c730c Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 12 Mar 2026 00:06:14 +0330 Subject: [PATCH 3/5] Revert "fix: bypass broken Nexus proxy, use nuget.org directly" This reverts commit 370f735cf2e72de1b1b492878c74f91614d83ce1. --- Dockerfile | 2 +- src/NuGet.config | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 96b8d02..68dcb91 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ COPY src/NuGet.config ./ # Copy solution and project files COPY src/ ./ -# Restore and publish +# Restore with Nexus config and publish RUN dotnet restore "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" --configfile NuGet.config RUN dotnet publish "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" -c Release -o /app/publish --no-restore diff --git a/src/NuGet.config b/src/NuGet.config index d70eceb..59d2375 100644 --- a/src/NuGet.config +++ b/src/NuGet.config @@ -2,11 +2,15 @@ - - + + + + + + From 0a66b09e7b8e72d4beb5fec1ee116d276f39587d Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 12 Mar 2026 01:14:07 +0330 Subject: [PATCH 4/5] ci: retry build - all NuGet packages pushed to Nexus From a35e2eeb92066a4a44567742f702556d5826dd66 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sun, 15 Mar 2026 03:12:03 +0330 Subject: [PATCH 5/5] fix: add proxy timeout annotations to CMS ingress Add nginx.ingress.kubernetes.io/proxy-read-timeout: 3600 and proxy-send-timeout: 3600 to prevent 504 Gateway Timeout on gRPC-Web requests through the nginx ingress controller. --- k8s/staging/cms-deployment.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/k8s/staging/cms-deployment.yaml b/k8s/staging/cms-deployment.yaml index b65aade..b5797a4 100644 --- a/k8s/staging/cms-deployment.yaml +++ b/k8s/staging/cms-deployment.yaml @@ -110,6 +110,8 @@ metadata: kubernetes.io/ingress.class: "nginx" cert-manager.io/cluster-issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" spec: tls: - hosts: