Merge kub-stage into production
Build and Deploy to Production / build-and-deploy (push) Successful in 11m49s

- Resolved appsettings.Production.json conflict (new MerchantId, SeedWorkers)
- Removed duplicate u21 migration (kept 155925 from production)
- All features: Magic Wallet, Discount Wallet, StockMovement fix, FullInformation expansion, Wallet user_name, gateway activation
This commit is contained in:
masoodafar-web
2026-02-22 22:21:36 +03:30
18 changed files with 5140 additions and 17 deletions
@@ -1,5 +1,6 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment;
using CMSMicroservice.Application.WalletCQ.Commands.VerifyDiscountWalletCharge;
using CMSMicroservice.Application.WalletCQ.Commands.VerifyMagicWalletCharge;
using MediatR;
using Microsoft.AspNetCore.Authorization;
@@ -186,4 +187,63 @@ public class PaymentCallbackController : ControllerBase
return Redirect($"{frontOfficeBaseUrl}/magic-wallet?payment=failed");
}
}
/// <summary>
/// Callback برای شارژ کیف‌پول تخفیفی — زرین‌پال بعد از پرداخت کاربر را اینجا برمی‌گرداند
/// </summary>
[HttpGet("/api/wallet/verify-discount-charge")]
public async Task<IActionResult> DiscountChargeCallback(
[FromQuery(Name = "Authority")] string? authority,
[FromQuery(Name = "Status")] string? status,
CancellationToken cancellationToken)
{
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
_logger.LogInformation(
"Discount charge callback received: Authority={Authority}, Status={Status}",
authority, status);
try
{
if (string.IsNullOrEmpty(authority))
{
_logger.LogError("Discount charge callback: Authority is missing");
return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=error&reason=no-authority");
}
// پیدا کردن PaymentTransaction برای استخراج UserId و Amount
var paymentTx = await _context.PaymentTransactions
.FirstOrDefaultAsync(pt => pt.Authority == authority, cancellationToken);
if (paymentTx == null || !paymentTx.UserId.HasValue)
{
_logger.LogError("Discount charge callback: PaymentTransaction not found for Authority={Authority}", authority);
return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=error&reason=tx-not-found");
}
if (!string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning("Discount charge cancelled by user. Authority={Authority}", authority);
return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=cancelled");
}
var result = await _sender.Send(new VerifyDiscountWalletChargeCommand
{
UserId = paymentTx.UserId.Value,
Amount = paymentTx.Amount,
Authority = authority
}, cancellationToken);
_logger.LogInformation(
"Discount charge completed successfully. Authority={Authority}, UserId={UserId}",
authority, paymentTx.UserId.Value);
return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=success");
}
catch (Exception ex)
{
_logger.LogError(ex, "Discount charge callback error. Authority={Authority}", authority);
return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=failed");
}
}
}
@@ -181,7 +181,9 @@ public class InventoryService : InventoryContract.InventoryContractBase
Id = inventoryItem.Id,
Quantity = Math.Abs(difference),
FromReserved = false,
ReferenceNumber = request.ReferenceNumber
ReferenceNumber = request.ReferenceNumber,
MovementType = Domain.Enums.StockMovementType.AdjustmentMinus,
Note = "Stock adjustment (decrease)"
},
context.CancellationToken);
@@ -332,7 +334,9 @@ public class InventoryService : InventoryContract.InventoryContractBase
Id = inventoryItem.Id,
Quantity = request.Quantity,
FromReserved = false,
ReferenceNumber = request.ReferenceNumber ?? $"LOSS-{DateTime.UtcNow:yyyyMMddHHmmss}"
ReferenceNumber = request.ReferenceNumber ?? $"LOSS-{DateTime.UtcNow:yyyyMMddHHmmss}",
MovementType = (Domain.Enums.StockMovementType)request.LossType,
Note = string.IsNullOrWhiteSpace(request.Reason) ? null : request.Reason
},
context.CancellationToken);
@@ -4,6 +4,7 @@ using CMSMicroservice.Application.UserWalletCQ.Commands.CreateNewUserWallet;
using CMSMicroservice.Application.UserWalletCQ.Commands.UpdateUserWallet;
using CMSMicroservice.Application.UserWalletCQ.Commands.DeleteUserWallet;
using CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet;
using CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet;
using CMSMicroservice.Application.UserWalletCQ.Queries.GetUserWallet;
using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter;
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog;
@@ -54,7 +55,28 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
}
public override async Task<GetAllUserWalletByFilterResponse> GetAllUserWalletByFilter(GetAllUserWalletByFilterRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetAllUserWalletByFilterRequest, GetAllUserWalletByFilterQuery, GetAllUserWalletByFilterResponse>(request, context);
var response = await _dispatchRequestToCQRS.Handle<GetAllUserWalletByFilterRequest, GetAllUserWalletByFilterQuery, GetAllUserWalletByFilterResponse>(request, context);
// Enrich response with user names
if (response?.Models != null && response.Models.Any())
{
var userIds = response.Models.Select(m => m.UserId).Distinct().ToList();
var users = await _context.Users
.AsNoTracking()
.Where(u => userIds.Contains(u.Id))
.Select(u => new { u.Id, u.FirstName, u.LastName })
.ToDictionaryAsync(u => u.Id, context.CancellationToken);
foreach (var model in response.Models)
{
if (users.TryGetValue(model.UserId, out var user))
{
model.UserName = $"{user.FirstName} {user.LastName}".Trim();
}
}
}
return response;
}
// ============= Customer-specific Methods =============
@@ -170,6 +192,27 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
};
}
// ============= Discount Wallet Methods =============
public override async Task<InitiateDiscountChargeResponse> InitiateDiscountCharge(
InitiateDiscountChargeRequest request, ServerCallContext context)
{
var userId = GetCurrentUserId();
var result = await _sender.Send(new ChargeDiscountWalletCommand
{
UserId = userId,
Amount = request.Amount
}, context.CancellationToken);
return new InitiateDiscountChargeResponse
{
IsSuccess = result.IsSuccess,
GatewayUrl = result.GatewayUrl ?? "",
ErrorMessage = result.ErrorMessage ?? ""
};
}
public override async Task<GetMagicWalletStatusResponse> GetMagicWalletStatus(
Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context)
{
@@ -181,6 +224,9 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
if (wallet == null)
throw new RpcException(new Status(StatusCode.NotFound, "کیف پول یافت نشد"));
var cycleCount = await _context.ClubMembershipCycles
.CountAsync(c => c.UserId == userId, context.CancellationToken);
var response = new GetMagicWalletStatusResponse
{
WalletMode = (int)wallet.WalletMode,
@@ -188,7 +234,8 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
MagicTotalCredited = wallet.MagicTotalCredited,
MagicMaxDeposit = SystemConstants.MagicWalletMaxDeposit,
MagicRemainingDeposit = Math.Max(0, SystemConstants.MagicWalletMaxDeposit - wallet.MagicTotalDeposited),
Balance = wallet.Balance
Balance = wallet.Balance,
PurchaseCycleCount = cycleCount
};
if (wallet.MagicActivatedAt.HasValue)
@@ -1,12 +1,11 @@
{
"PaymentProvider": "zarinpal",
"ZarinPal": {
"MerchantId": "6b098fc8-f490-47a1-aac3-1de1a1b84404",
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
"UseSandbox": false
},
"CmsBaseUrl": "https://cms.kbs1.ir",
"FrontOfficeBaseUrl": "https://kbs1.ir",
"UseRealPaymentGateway": false,
"JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=",
"JwtIssuer": "https://localhost",
"JwtAudience": "https://localhost",
@@ -68,6 +67,11 @@
"CronExpression": "5 0 * * 0"
}
},
"SeedWorkers": {
"MagicWalletCycleSeed": {
"Enabled": true
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
@@ -1,7 +1,7 @@
{
"PaymentProvider": "zarinpal",
"ZarinPal": {
"MerchantId": "6b098fc8-f490-47a1-aac3-1de1a1b84404",
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
"UseSandbox": true
},
"FMS": {
@@ -68,6 +68,11 @@
"CronExpression": "5 0 * * 0"
}
},
"SeedWorkers": {
"MagicWalletCycleSeed": {
"Enabled": true
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
+6 -1
View File
@@ -1,7 +1,7 @@
{
"PaymentProvider": "zarinpal",
"ZarinPal": {
"MerchantId": "6b098fc8-f490-47a1-aac3-1de1a1b84404",
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
"UseSandbox": true
},
"CmsBaseUrl": "https://cms.kbs1.ir",
@@ -75,6 +75,11 @@
"CronExpression": "5 0 * * 0"
}
},
"SeedWorkers": {
"MagicWalletCycleSeed": {
"Enabled": true
}
},
"AllowedHosts": "*",
"Authentication": {
"Authority": "https://ids.domain.com/",