feat: ChargeDiscountWallet - callback endpoint, gRPC RPC & service implementation
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 10m43s

- Add /api/wallet/verify-discount-charge callback endpoint in PaymentCallbackController
- Add InitiateDiscountCharge RPC + request/response messages in userwallet.proto
- Add InitiateDiscountCharge gRPC service override in UserWalletService
- Add ChargeDiscountWallet using import for handler resolution
This commit is contained in:
masoodafar-web
2026-02-22 20:22:44 +03:30
parent 58d419ced3
commit b90aa50fac
3 changed files with 105 additions and 0 deletions
@@ -88,6 +88,15 @@ service UserWalletContract
get: "/Customer/GetMagicWalletStatus"
};
};
// ============= Discount Wallet Methods =============
rpc InitiateDiscountCharge(InitiateDiscountChargeRequest) returns (InitiateDiscountChargeResponse){
option (google.api.http) = {
post: "/Customer/InitiateDiscountCharge"
body: "*"
};
};
}
message CreateNewUserWalletRequest
{
@@ -242,3 +251,17 @@ message GetMagicWalletStatusResponse
google.protobuf.Timestamp magic_activated_at = 7;
int32 purchase_cycle_count = 8; // تعداد دورهای خرید پکیج (0 = هنوز هیچ دوری تکمیل نشده)
}
// ============= Discount Wallet Messages =============
message InitiateDiscountChargeRequest
{
int64 amount = 1; // مبلغ واریزی (ریال)
}
message InitiateDiscountChargeResponse
{
bool is_success = 1;
string gateway_url = 2;
string error_message = 3;
}
@@ -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");
}
}
}
@@ -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;
@@ -170,6 +171,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)
{