Merge branch 'kub-stage' into production
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -32,7 +32,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
private readonly ISender _sender;
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly IPaymentGatewayResolver _paymentGatewayResolver;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IUserPaymentLock _paymentLock;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
ISender sender,
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUserService,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
IPaymentGatewayResolver paymentGatewayResolver,
|
||||
IConfiguration configuration,
|
||||
IUserPaymentLock paymentLock)
|
||||
{
|
||||
@@ -49,7 +49,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
_sender = sender;
|
||||
_context = context;
|
||||
_currentUserService = currentUserService;
|
||||
_paymentGateway = paymentGateway;
|
||||
_paymentGatewayResolver = paymentGatewayResolver;
|
||||
_configuration = configuration;
|
||||
_paymentLock = paymentLock;
|
||||
}
|
||||
@@ -171,19 +171,38 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
|
||||
if (package == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "پکیج مورد نظر یافت نشد"));
|
||||
|
||||
// Create transaction
|
||||
|
||||
var gatewayProvider = NormalizeGatewayProvider(request.GatewayProvider);
|
||||
var isTwentyCard = gatewayProvider == "20card";
|
||||
|
||||
long gatewayAmount = package.Price;
|
||||
if (isTwentyCard)
|
||||
{
|
||||
var packageKey = $"TwentyCard:Packages:{package.Id}";
|
||||
gatewayAmount = _configuration.GetValue<long?>($"{packageKey}:GatewayAmountToman") ?? 0;
|
||||
var walletCredit = _configuration.GetValue<long?>($"{packageKey}:WalletCreditToman") ?? 0;
|
||||
if (gatewayAmount <= 0 || walletCredit <= 0)
|
||||
{
|
||||
throw new RpcException(new Status(
|
||||
StatusCode.FailedPrecondition,
|
||||
"پرداخت ۲۰کارت برای این پکیج پیکربندی نشده است"));
|
||||
}
|
||||
}
|
||||
|
||||
// Create transaction — 20card: Amount = gateway charge; zarinpal: Package.Price
|
||||
var transaction = new CMSMicroservice.Domain.Entities.Transaction
|
||||
{
|
||||
Amount = package.Price,
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
Amount = gatewayAmount,
|
||||
Description = isTwentyCard
|
||||
? $"خرید پکیج {package.Title} (۲۰کارت)"
|
||||
: $"خرید پکیج {package.Title}",
|
||||
PaymentStatus = Domain.Enums.PaymentStatus.Pending,
|
||||
Type = Domain.Enums.TransactionType.Buy
|
||||
};
|
||||
_context.Transactions.Add(transaction);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Create purchase record
|
||||
// Create purchase record — UPP.Amount always Package.Price (catalog)
|
||||
var purchaseMethod = request.PurchaseMethod == PurchaseMethodEnum.PurchaseMethodGateway
|
||||
? Domain.Enums.PackagePurchaseMethod.DirectPurchase
|
||||
: Domain.Enums.PackagePurchaseMethod.DayaLoan;
|
||||
@@ -204,20 +223,32 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
var user = await _context.Users
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == userId)
|
||||
.Select(u => new { u.Mobile })
|
||||
.Select(u => new { u.Mobile, u.FirstName, u.LastName, u.Email, u.NationalCode })
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
// Callback URL از config — نه از ورودی کاربر (امنیت)
|
||||
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
||||
var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?orderId={purchase.Id}";
|
||||
// ۲۰کارت: CallbackBaseUrl اختیاری (مثلاً استیج → kbs2.ir چون دامنه استیج در ترمینال ثبت نیست)
|
||||
var frontOfficeBaseUrl = isTwentyCard
|
||||
? (_configuration["TwentyCard:CallbackBaseUrl"]
|
||||
?? _configuration["FrontOfficeBaseUrl"]
|
||||
?? "https://localhost:5268")
|
||||
: (_configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268");
|
||||
var callbackUrl = $"{frontOfficeBaseUrl.TrimEnd('/')}/profile/payment-callback?orderId={purchase.Id}";
|
||||
|
||||
var paymentGateway = _paymentGatewayResolver.Get(gatewayProvider);
|
||||
var payerName = $"{user?.FirstName} {user?.LastName}".Trim();
|
||||
|
||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
||||
var paymentResult = await paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
||||
{
|
||||
Amount = package.Price,
|
||||
Amount = gatewayAmount,
|
||||
UserId = userId,
|
||||
Mobile = user?.Mobile ?? string.Empty,
|
||||
Name = payerName,
|
||||
Invoice = purchase.Id.ToString(),
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
CallbackUrl = callbackUrl
|
||||
CallbackUrl = callbackUrl,
|
||||
Email = user?.Email,
|
||||
NationalId = user?.NationalCode
|
||||
}, cancellationToken);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
@@ -235,12 +266,14 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
// Save RefId
|
||||
transaction.RefId = paymentResult.RefId;
|
||||
|
||||
// ثبت PaymentTransaction
|
||||
// ثبت PaymentTransaction — Amount = gateway amount
|
||||
var paymentTx = new PaymentTransaction
|
||||
{
|
||||
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
|
||||
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
|
||||
Amount = package.Price,
|
||||
GatewayProvider = gatewayProvider,
|
||||
MerchantId = isTwentyCard
|
||||
? (_configuration["TwentyCard:TerminalId"] ?? "")
|
||||
: (_configuration["ZarinPal:MerchantId"] ?? ""),
|
||||
Amount = gatewayAmount,
|
||||
CallbackUrl = callbackUrl,
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
Mobile = user?.Mobile,
|
||||
@@ -264,6 +297,14 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
Authority = paymentResult.RefId ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static string NormalizeGatewayProvider(string? provider)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(provider))
|
||||
return "zarinpal";
|
||||
var key = provider.Trim().ToLowerInvariant();
|
||||
return key is "20card" or "twentycard" ? "20card" : "zarinpal";
|
||||
}
|
||||
|
||||
public override async Task<CustomerVerifyPackagePurchaseResponse> CustomerVerifyPackagePurchase(
|
||||
CustomerVerifyPackagePurchaseRequest request,
|
||||
|
||||
@@ -15,8 +15,11 @@ using CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken;
|
||||
using CMSMicroservice.Application.UserCQ.Commands.AcceptContract;
|
||||
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile;
|
||||
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals;
|
||||
using CMSMicroservice.Application.UserCQ.Queries.GetMyInvitees;
|
||||
using CMSMicroservice.Application.UserCQ.Queries.GetInvitationsByFilter;
|
||||
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common.Authorization;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Grpc.Core;
|
||||
@@ -312,6 +315,117 @@ public class UserService : UserContract.UserContractBase
|
||||
return response;
|
||||
}
|
||||
|
||||
public override async Task<GetMyInviteesResponse> GetMyInvitees(
|
||||
GetMyInviteesRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var result = await _sender.Send(new GetMyInviteesQuery
|
||||
{
|
||||
UserId = 0,
|
||||
PaginationState = request.PaginationState?.Adapt<AppModels.PaginationState>(),
|
||||
ClubStatusFilter = request.ClubStatusFilter
|
||||
}, context.CancellationToken);
|
||||
|
||||
var response = new GetMyInviteesResponse
|
||||
{
|
||||
MetaData = new MetaData
|
||||
{
|
||||
CurrentPage = result.MetaData.CurrentPage,
|
||||
TotalPage = result.MetaData.TotalPage,
|
||||
PageSize = result.MetaData.PageSize,
|
||||
TotalCount = result.MetaData.TotalCount,
|
||||
HasPrevious = result.MetaData.HasPrevious,
|
||||
HasNext = result.MetaData.HasNext
|
||||
},
|
||||
Stats = new InviteeStats
|
||||
{
|
||||
TotalInvitees = result.Stats.TotalInvitees,
|
||||
ClubActiveCount = result.Stats.ClubActiveCount,
|
||||
ClubInactiveCount = result.Stats.ClubInactiveCount
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var item in result.Invitees)
|
||||
{
|
||||
response.Invitees.Add(new InviteeModel
|
||||
{
|
||||
UserId = item.UserId,
|
||||
FirstName = item.FirstName,
|
||||
LastName = item.LastName,
|
||||
Mobile = item.Mobile,
|
||||
RegisteredAt = Timestamp.FromDateTime(DateTime.SpecifyKind(item.RegisteredAt, DateTimeKind.Utc)),
|
||||
IsClubActive = item.IsClubActive
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.UsersView)]
|
||||
public override async Task<GetInvitationsByFilterResponse> GetInvitationsByFilter(
|
||||
GetInvitationsByFilterRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
DateTime? createdFrom = null;
|
||||
DateTime? createdTo = null;
|
||||
if (request.Filter?.CreatedFrom != null)
|
||||
createdFrom = request.Filter.CreatedFrom.ToDateTime();
|
||||
if (request.Filter?.CreatedTo != null)
|
||||
createdTo = request.Filter.CreatedTo.ToDateTime();
|
||||
|
||||
var result = await _sender.Send(new GetInvitationsByFilterQuery
|
||||
{
|
||||
PaginationState = request.PaginationState?.Adapt<AppModels.PaginationState>(),
|
||||
SortBy = request.SortBy,
|
||||
Filter = request.Filter == null ? null : new GetInvitationsByFilterFilterDto
|
||||
{
|
||||
InviterMobile = request.Filter.InviterMobile,
|
||||
InviterFirstName = request.Filter.InviterFirstName,
|
||||
InviterLastName = request.Filter.InviterLastName,
|
||||
InviteeMobile = request.Filter.InviteeMobile,
|
||||
InviteeFirstName = request.Filter.InviteeFirstName,
|
||||
InviteeLastName = request.Filter.InviteeLastName,
|
||||
InviteeClubActive = request.Filter.InviteeClubActive,
|
||||
CreatedFrom = createdFrom,
|
||||
CreatedTo = createdTo
|
||||
}
|
||||
}, context.CancellationToken);
|
||||
|
||||
var response = new GetInvitationsByFilterResponse
|
||||
{
|
||||
MetaData = new MetaData
|
||||
{
|
||||
CurrentPage = result.MetaData.CurrentPage,
|
||||
TotalPage = result.MetaData.TotalPage,
|
||||
PageSize = result.MetaData.PageSize,
|
||||
TotalCount = result.MetaData.TotalCount,
|
||||
HasPrevious = result.MetaData.HasPrevious,
|
||||
HasNext = result.MetaData.HasNext
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var m in result.Models)
|
||||
{
|
||||
response.Models.Add(new InvitationReportModel
|
||||
{
|
||||
Id = m.Id,
|
||||
InviterUserId = m.InviterUserId,
|
||||
InviterFirstName = m.InviterFirstName,
|
||||
InviterLastName = m.InviterLastName,
|
||||
InviterMobile = m.InviterMobile,
|
||||
InviteeUserId = m.InviteeUserId,
|
||||
InviteeFirstName = m.InviteeFirstName,
|
||||
InviteeLastName = m.InviteeLastName,
|
||||
InviteeMobile = m.InviteeMobile,
|
||||
InviteeRegisteredAt = Timestamp.FromDateTime(DateTime.SpecifyKind(m.InviteeRegisteredAt, DateTimeKind.Utc)),
|
||||
InviteeClubActive = m.InviteeClubActive,
|
||||
InvitedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(m.InvitedAt, DateTimeKind.Utc))
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public override async Task<UploadCustomerAvatarResponse> UploadCustomerAvatar(UploadCustomerAvatarRequest request, ServerCallContext context)
|
||||
{
|
||||
if (request.FileData == null || request.FileData.Length == 0)
|
||||
|
||||
@@ -4,6 +4,24 @@
|
||||
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
|
||||
"UseSandbox": false
|
||||
},
|
||||
"TwentyCard": {
|
||||
"BaseUrl": "https://gateway.20card.ir/ipg",
|
||||
"TerminalId": "165674",
|
||||
"TerminalUsername": "ea5y1biehi",
|
||||
"TerminalPassword": "SaLamAT@1404!",
|
||||
"CreditPortalUrl": "https://userpanel.20card.ir/",
|
||||
"DefaultEmail": "info@kbs2.ir",
|
||||
"Packages": {
|
||||
"4": {
|
||||
"GatewayAmountToman": 9700000,
|
||||
"WalletCreditToman": 13000000
|
||||
},
|
||||
"1": {
|
||||
"GatewayAmountToman": 97000000,
|
||||
"WalletCreditToman": 130000000
|
||||
}
|
||||
}
|
||||
},
|
||||
"CmsBaseUrl": "https://cms.kbs2.ir",
|
||||
"FrontOfficeBaseUrl": "https://kbs2.ir",
|
||||
"JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=",
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"PaymentProvider": "zarinpal",
|
||||
"ZarinPal": {
|
||||
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
|
||||
"UseSandbox": true
|
||||
},
|
||||
"TwentyCard": {
|
||||
"BaseUrl": "https://gateway.20card.ir/ipg",
|
||||
"TerminalId": "165674",
|
||||
"TerminalUsername": "ea5y1biehi",
|
||||
"TerminalPassword": "SaLamAT@1404!",
|
||||
"CreditPortalUrl": "https://userpanel.20card.ir/",
|
||||
"CallbackBaseUrl": "https://kbs2.ir",
|
||||
"DefaultEmail": "info@kbs2.ir",
|
||||
"Packages": {
|
||||
"4": {
|
||||
"GatewayAmountToman": 1000,
|
||||
"WalletCreditToman": 13000000
|
||||
},
|
||||
"1": {
|
||||
"GatewayAmountToman": 1000,
|
||||
"WalletCreditToman": 130000000
|
||||
}
|
||||
}
|
||||
},
|
||||
"CmsBaseUrl": "https://cms.se.kbs1.ir",
|
||||
"FrontOfficeBaseUrl": "https://frontoffice.se.kbs1.ir",
|
||||
"FMS": {
|
||||
"Address": "https://dl.afrino.co"
|
||||
},
|
||||
"JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=",
|
||||
"JwtIssuer": "https://localhost",
|
||||
"JwtAudience": "https://localhost",
|
||||
"JwtExpiryInDays": 5,
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Data Source=194.5.195.53,31433; Initial Catalog=Foursat;User ID=sa;Password=87zH26nbqT;Connection Timeout=300000;MultipleActiveResultSets=True;Encrypt=False",
|
||||
"providerName": "System.Data.SqlClient"
|
||||
},
|
||||
"Otp": {
|
||||
"Secret": "K2w8k1h1mH2Qz1kqWk0c8kQ2Pq8q9H1eE2nqN1qQ8x7M=",
|
||||
"MasterCode": "1122334455"
|
||||
},
|
||||
"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": true,
|
||||
"CronExpression": "5 0 * * 0"
|
||||
}
|
||||
},
|
||||
"SeedWorkers": {
|
||||
"MagicWalletCycleSeed": {
|
||||
"Enabled": true
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Kestrel": {
|
||||
"EndpointDefaults": {
|
||||
"Protocols": "Http2"
|
||||
}
|
||||
},
|
||||
"Authentication": {
|
||||
"Authority": "https://ids.domain.com/",
|
||||
"Audience": "domain_api"
|
||||
},
|
||||
"Seq": {
|
||||
"ServerUrl": "https://seq.afrino.co",
|
||||
"ApiKey": "oxpvpUzU1pZxMS4s3Fqq"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,34 @@
|
||||
"PaymentProvider": "zarinpal",
|
||||
"ZarinPal": {
|
||||
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
|
||||
<<<<<<< HEAD
|
||||
"UseSandbox": false
|
||||
=======
|
||||
"UseSandbox": true
|
||||
},
|
||||
"TwentyCard": {
|
||||
"BaseUrl": "https://gateway.20card.ir/ipg",
|
||||
"TerminalId": "165674",
|
||||
"TerminalUsername": "ea5y1biehi",
|
||||
"TerminalPassword": "SaLamAT@1404!",
|
||||
"CreditPortalUrl": "https://userpanel.20card.ir/",
|
||||
"DefaultEmail": "info@kbs2.ir",
|
||||
"Packages": {
|
||||
"4": {
|
||||
"GatewayAmountToman": 9700000,
|
||||
"WalletCreditToman": 13000000
|
||||
},
|
||||
"1": {
|
||||
"GatewayAmountToman": 97000000,
|
||||
"WalletCreditToman": 130000000
|
||||
}
|
||||
}
|
||||
},
|
||||
"CmsBaseUrl": "https://localhost:32846",
|
||||
"FrontOfficeBaseUrl": "http://localhost:5268",
|
||||
"FMS": {
|
||||
"Address": "https://dl.afrino.co"
|
||||
>>>>>>> kub-stage
|
||||
},
|
||||
"CmsBaseUrl": "https://cms.kbs2.ir",
|
||||
"FrontOfficeBaseUrl": "https://kbs2.ir",
|
||||
|
||||
Reference in New Issue
Block a user