feat: implement user-based wallet and transaction retrieval by parsing JWT sub claim and adjust JWT validation parameters across services.

This commit is contained in:
Ho Ngoc Hai
2026-03-04 13:08:08 +07:00
parent 7baba14fad
commit 028ef4c1cd
6 changed files with 115 additions and 16 deletions

View File

@@ -22,28 +22,112 @@ public class FinancialController : ControllerBase
}
/// <summary>
/// EN: Get wallets for the current merchant.
/// VI: Lấy ví của merchant hiện tại.
/// EN: Extract userId from JWT Bearer token in the Authorization header.
/// VI: Trích xuất userId từ JWT Bearer token trong header Authorization.
/// </summary>
private Guid? GetUserIdFromToken()
{
var authHeader = Request.Headers["Authorization"].FirstOrDefault();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ")) return null;
var token = authHeader["Bearer ".Length..];
var parts = token.Split('.');
if (parts.Length != 3) return null;
var payload = parts[1];
// EN: Fix base64url padding / VI: Sửa padding base64url
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
payload = payload.Replace('-', '+').Replace('_', '/');
try
{
var json = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(payload));
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("sub", out var sub) && Guid.TryParse(sub.GetString(), out var userId))
return userId;
}
catch { /* invalid token format */ }
return null;
}
/// <summary>
/// EN: Get wallet for the current user (extracted from JWT sub claim).
/// VI: Lấy ví của user hiện tại (trích từ JWT sub claim).
/// </summary>
[HttpGet("wallets")]
public Task<IActionResult> GetWallets() =>
_wallet.GetAsync("/api/v1/wallets").ProxyAsync();
public async Task<IActionResult> GetWallets()
{
var userId = GetUserIdFromToken();
if (userId == null)
return Unauthorized(new { message = "Cannot extract user ID from token" });
// EN: WalletService returns single wallet; wrap in array for frontend compatibility.
// VI: WalletService trả về 1 ví; bọc trong array cho tương thích frontend.
var response = await _wallet.GetAsync($"/api/v1/wallets/{userId}");
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
// EN: If 404, return empty array (user has no wallet yet).
// VI: Nếu 404, trả array rỗng (user chưa có ví).
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
return new ContentResult { StatusCode = 200, Content = "[]", ContentType = "application/json" };
return new ContentResult
{
StatusCode = (int)response.StatusCode,
Content = content,
ContentType = "application/json"
};
}
// EN: Extract wallet data from ApiResponse envelope and wrap in array.
// VI: Trích dữ liệu ví từ ApiResponse envelope và bọc trong array.
try
{
using var doc = JsonDocument.Parse(content);
if (doc.RootElement.TryGetProperty("data", out var data) && data.ValueKind == JsonValueKind.Object)
return new ContentResult { StatusCode = 200, Content = $"[{data.GetRawText()}]", ContentType = "application/json" };
}
catch { /* fallback */ }
return new ContentResult { StatusCode = 200, Content = $"[{content}]", ContentType = "application/json" };
}
/// <summary>
/// EN: Get wallet transactions for the current merchant.
/// VI: Lấy giao dịch ví của merchant hiện tại.
/// EN: Get wallet transactions for the current user.
/// VI: Lấy giao dịch ví của user hiện tại.
/// </summary>
[HttpGet("wallet/transactions")]
public Task<IActionResult> GetWalletTransactions([FromQuery] int limit = 50) =>
_wallet.GetAsync($"/api/v1/wallet/transactions?limit={limit}").ProxyAsync();
public async Task<IActionResult> GetWalletTransactions([FromQuery] int limit = 50)
{
var userId = GetUserIdFromToken();
if (userId == null)
return Unauthorized(new { message = "Cannot extract user ID from token" });
var response = await _wallet.GetAsync($"/api/v1/wallets/{userId}/transactions?limit={limit}");
// EN: If wallet not found, return empty array (user has no wallet yet).
// VI: Nếu ví không tồn tại, trả array rỗng (user chưa có ví).
if (!response.IsSuccessStatusCode)
return new ContentResult { StatusCode = 200, Content = "[]", ContentType = "application/json" };
var content = await response.Content.ReadAsStringAsync();
return new ContentResult { StatusCode = 200, Content = content, ContentType = "application/json" };
}
/// <summary>
/// EN: Get campaigns for current merchant.
/// VI: Lấy danh sách chiến dịch của merchant hiện tại.
/// EN: Get promotions for current merchant.
/// VI: Lấy danh sách khuyến mãi của merchant hiện tại.
/// </summary>
[HttpGet("promotions")]
public Task<IActionResult> GetPromotions() =>
_promotion.GetAsync("/api/v1/promotions").ProxyAsync();
_promotion.GetAsync("/api/v1/campaigns").ProxyAsync();
/// <summary>
/// EN: Get campaigns for current merchant.