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:
@@ -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.
|
||||
|
||||
@@ -212,6 +212,11 @@ services:
|
||||
# VI: Giao tiếp IAM Service
|
||||
- IamService__BaseUrl=http://iam-service-net:8080
|
||||
- IamService__ServiceName=membership-service
|
||||
# EN: JWT Configuration
|
||||
# VI: Cấu hình JWT
|
||||
- Jwt__Authority=http://iam-service-net:8080
|
||||
- Jwt__Audience=goodgo-api
|
||||
- Jwt__RequireHttpsMetadata=false
|
||||
ports:
|
||||
- "5003:8080"
|
||||
depends_on:
|
||||
|
||||
@@ -137,8 +137,13 @@ try
|
||||
.AddJwtBearer("Bearer", options =>
|
||||
{
|
||||
options.Authority = builder.Configuration["Jwt:Authority"] ?? "http://localhost:5001";
|
||||
options.Audience = builder.Configuration["Jwt:Audience"] ?? "membership-service";
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = false,
|
||||
ValidateAudience = false,
|
||||
ValidateLifetime = true,
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
@@ -74,7 +74,8 @@ public class MemberEntityTypeConfiguration : IEntityTypeConfiguration<Member>
|
||||
|
||||
// EN: Soft delete
|
||||
// VI: Xóa mềm
|
||||
builder.Property("_isDeleted")
|
||||
builder.Property(m => m.IsDeleted)
|
||||
.HasField("_isDeleted")
|
||||
.HasColumnName("is_deleted")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
@@ -93,7 +94,7 @@ public class MemberEntityTypeConfiguration : IEntityTypeConfiguration<Member>
|
||||
builder.HasIndex("_currentExp")
|
||||
.HasDatabaseName("ix_members_current_exp");
|
||||
|
||||
builder.HasIndex("_isDeleted")
|
||||
builder.HasIndex(m => m.IsDeleted)
|
||||
.HasDatabaseName("ix_members_is_deleted");
|
||||
|
||||
// EN: Ignore domain events (not persisted)
|
||||
|
||||
@@ -63,6 +63,10 @@ public class MembershipServiceContext : DbContext, IUnitOfWork
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// EN: Ignore Enumeration types to prevent EF Core discovery issues
|
||||
// VI: Bỏ qua Enumeration types để tránh lỗi EF Core discovery
|
||||
modelBuilder.Ignore<ExperienceSource>();
|
||||
|
||||
// EN: Apply entity configurations
|
||||
// VI: Áp dụng entity configurations
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(MembershipServiceContext).Assembly);
|
||||
|
||||
@@ -115,8 +115,8 @@ try
|
||||
options.TokenValidationParameters = new()
|
||||
{
|
||||
ValidateAudience = false,
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = builder.Configuration["Jwt:Issuer"]
|
||||
ValidateIssuer = false,
|
||||
ValidateLifetime = true,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user