diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/FinancialController.cs b/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/FinancialController.cs
index 9ceca259..64f9dca8 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/FinancialController.cs
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/FinancialController.cs
@@ -22,28 +22,112 @@ public class FinancialController : ControllerBase
}
///
- /// 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.
+ ///
+ 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;
+ }
+
+ ///
+ /// 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).
///
[HttpGet("wallets")]
- public Task GetWallets() =>
- _wallet.GetAsync("/api/v1/wallets").ProxyAsync();
+ public async Task 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" };
+ }
///
- /// 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.
///
[HttpGet("wallet/transactions")]
- public Task GetWalletTransactions([FromQuery] int limit = 50) =>
- _wallet.GetAsync($"/api/v1/wallet/transactions?limit={limit}").ProxyAsync();
+ public async Task 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" };
+ }
///
- /// 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.
///
[HttpGet("promotions")]
public Task GetPromotions() =>
- _promotion.GetAsync("/api/v1/promotions").ProxyAsync();
+ _promotion.GetAsync("/api/v1/campaigns").ProxyAsync();
///
/// EN: Get campaigns for current merchant.
diff --git a/deployments/local/docker-compose.yml b/deployments/local/docker-compose.yml
index 5d1d2fab..bf605be0 100644
--- a/deployments/local/docker-compose.yml
+++ b/deployments/local/docker-compose.yml
@@ -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:
diff --git a/services/membership-service-net/src/MembershipService.API/Program.cs b/services/membership-service-net/src/MembershipService.API/Program.cs
index 05fd84c2..d9cba3dd 100644
--- a/services/membership-service-net/src/MembershipService.API/Program.cs
+++ b/services/membership-service-net/src/MembershipService.API/Program.cs
@@ -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();
diff --git a/services/membership-service-net/src/MembershipService.Infrastructure/EntityConfigurations/MemberEntityTypeConfiguration.cs b/services/membership-service-net/src/MembershipService.Infrastructure/EntityConfigurations/MemberEntityTypeConfiguration.cs
index ab49c8b5..e1d20d61 100644
--- a/services/membership-service-net/src/MembershipService.Infrastructure/EntityConfigurations/MemberEntityTypeConfiguration.cs
+++ b/services/membership-service-net/src/MembershipService.Infrastructure/EntityConfigurations/MemberEntityTypeConfiguration.cs
@@ -74,7 +74,8 @@ public class MemberEntityTypeConfiguration : IEntityTypeConfiguration
// 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
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)
diff --git a/services/membership-service-net/src/MembershipService.Infrastructure/MembershipServiceContext.cs b/services/membership-service-net/src/MembershipService.Infrastructure/MembershipServiceContext.cs
index d97d4ca9..a5e048fb 100644
--- a/services/membership-service-net/src/MembershipService.Infrastructure/MembershipServiceContext.cs
+++ b/services/membership-service-net/src/MembershipService.Infrastructure/MembershipServiceContext.cs
@@ -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();
+
// EN: Apply entity configurations
// VI: Áp dụng entity configurations
modelBuilder.ApplyConfigurationsFromAssembly(typeof(MembershipServiceContext).Assembly);
diff --git a/services/wallet-service-net/src/WalletService.API/Program.cs b/services/wallet-service-net/src/WalletService.API/Program.cs
index cf9cb504..e5054e3b 100644
--- a/services/wallet-service-net/src/WalletService.API/Program.cs
+++ b/services/wallet-service-net/src/WalletService.API/Program.cs
@@ -115,8 +115,8 @@ try
options.TokenValidationParameters = new()
{
ValidateAudience = false,
- ValidateIssuer = true,
- ValidIssuer = builder.Configuration["Jwt:Issuer"]
+ ValidateIssuer = false,
+ ValidateLifetime = true,
};
});