diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Commands/AdminAdCommandHandlers.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Commands/AdminAdCommandHandlers.cs
new file mode 100644
index 00000000..c1cbe809
--- /dev/null
+++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Commands/AdminAdCommandHandlers.cs
@@ -0,0 +1,91 @@
+using AdsManagerService.Domain.AggregatesModel.AdAggregate;
+using AdsManagerService.Domain.SeedWork;
+using AdsManagerService.Infrastructure;
+using MediatR;
+using Microsoft.Extensions.Logging;
+
+namespace AdsManagerService.API.Application.Commands;
+
+///
+/// EN: Handler for approving ad.
+/// VI: Handler phê duyệt quảng cáo.
+///
+public class ApproveAdCommandHandler : IRequestHandler
+{
+ private readonly IAdRepository _adRepository;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly ILogger _logger;
+
+ public ApproveAdCommandHandler(
+ IAdRepository adRepository,
+ IUnitOfWork unitOfWork,
+ ILogger logger)
+ {
+ _adRepository = adRepository ?? throw new ArgumentNullException(nameof(adRepository));
+ _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task Handle(Controllers.ApproveAdCommand request, CancellationToken cancellationToken)
+ {
+ var ad = await _adRepository.GetByIdAsync(request.AdId);
+
+ if (ad == null)
+ {
+ _logger.LogWarning("Ad {AdId} not found", request.AdId);
+ return false;
+ }
+
+ // EN: Approve ad (domain method handles state transition)
+ // VI: Phê duyệt quảng cáo (domain method xử lý chuyển trạng thái)
+ ad.Approve();
+
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ _logger.LogInformation("Ad {AdId} approved successfully", request.AdId);
+
+ return true;
+ }
+}
+
+///
+/// EN: Handler for rejecting ad.
+/// VI: Handler từ chối quảng cáo.
+///
+public class RejectAdCommandHandler : IRequestHandler
+{
+ private readonly IAdRepository _adRepository;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly ILogger _logger;
+
+ public RejectAdCommandHandler(
+ IAdRepository adRepository,
+ IUnitOfWork unitOfWork,
+ ILogger logger)
+ {
+ _adRepository = adRepository ?? throw new ArgumentNullException(nameof(adRepository));
+ _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task Handle(Controllers.RejectAdCommand request, CancellationToken cancellationToken)
+ {
+ var ad = await _adRepository.GetByIdAsync(request.AdId);
+
+ if (ad == null)
+ {
+ _logger.LogWarning("Ad {AdId} not found", request.AdId);
+ return false;
+ }
+
+ // EN: Reject ad with reason
+ // VI: Từ chối quảng cáo với lý do
+ ad.Reject(request.Reason);
+
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ _logger.LogInformation("Ad {AdId} rejected with reason: {Reason}", request.AdId, request.Reason);
+
+ return true;
+ }
+}
diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/AdminReportsQueryHandlers.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/AdminReportsQueryHandlers.cs
new file mode 100644
index 00000000..e91e6794
--- /dev/null
+++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/AdminReportsQueryHandlers.cs
@@ -0,0 +1,86 @@
+using AdsManagerService.Infrastructure;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AdsManagerService.API.Controllers;
+
+///
+/// EN: Handler for getting top advertisers by spend.
+/// VI: Handler lấy top advertisers theo chi tiêu.
+///
+public class GetTopAdvertisersQueryHandler : IRequestHandler>
+{
+ private readonly AdsManagerServiceContext _context;
+
+ public GetTopAdvertisersQueryHandler(AdsManagerServiceContext context)
+ {
+ _context = context ?? throw new ArgumentNullException(nameof(context));
+ }
+
+ public async Task> Handle(GetTopAdvertisersQuery request, CancellationToken cancellationToken)
+ {
+ var topAdvertisers = await _context.Campaigns
+ .GroupBy(c => c.AdvertiserId)
+ .Select(g => new TopAdvertiserDto
+ {
+ AdvertiserId = g.Key,
+ TotalCampaigns = g.Count(),
+ TotalSpend = g.Sum(c => c.TotalSpend),
+ ActiveCampaigns = g.Count(c => c.Status.Name == "Active")
+ })
+ .OrderByDescending(a => a.TotalSpend)
+ .Take(request.Limit)
+ .ToListAsync(cancellationToken);
+
+ return topAdvertisers;
+ }
+}
+
+///
+/// EN: Handler for getting revenue analytics.
+/// VI: Handler lấy phân tích doanh thu.
+///
+public class GetRevenueAnalyticsQueryHandler : IRequestHandler
+{
+ private readonly AdsManagerServiceContext _context;
+
+ public GetRevenueAnalyticsQueryHandler(AdsManagerServiceContext context)
+ {
+ _context = context ?? throw new ArgumentNullException(nameof(context));
+ }
+
+ public async Task Handle(GetRevenueAnalyticsQuery request, CancellationToken cancellationToken)
+ {
+ var query = _context.Campaigns.AsQueryable();
+
+ // EN: Filter by date range if provided
+ // VI: Lọc theo khoảng thời gian nếu có
+ if (request.StartDate.HasValue)
+ query = query.Where(c => c.CreatedAt >= request.StartDate.Value);
+
+ if (request.EndDate.HasValue)
+ query = query.Where(c => c.CreatedAt <= request.EndDate.Value);
+
+ var campaigns = await query.ToListAsync(cancellationToken);
+
+ var totalRevenue = campaigns.Sum(c => c.TotalSpend);
+ var totalCampaigns = campaigns.Count;
+
+ // EN: Group revenue by objective
+ // VI: Nhóm doanh thu theo mục tiêu
+ var revenueByObjective = campaigns
+ .GroupBy(c => c.Objective.Name)
+ .ToDictionary(
+ g => g.Key,
+ g => g.Sum(c => c.TotalSpend)
+ );
+
+ return new RevenueAnalyticsDto
+ {
+ TotalRevenue = totalRevenue,
+ AverageRevenuePerCampaign = totalCampaigns > 0 ? totalRevenue / totalCampaigns : 0,
+ TotalCampaigns = totalCampaigns,
+ RevenueByObjective = revenueByObjective
+ };
+ }
+}
diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/GetCampaignStatsQueryHandler.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/GetCampaignStatsQueryHandler.cs
new file mode 100644
index 00000000..aeccd7d6
--- /dev/null
+++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/GetCampaignStatsQueryHandler.cs
@@ -0,0 +1,35 @@
+using AdsManagerService.Infrastructure;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AdsManagerService.API.Controllers;
+
+///
+/// EN: Handler for getting campaign statistics.
+/// VI: Handler lấy thống kê chiến dịch.
+///
+public class GetCampaignStatsQueryHandler : IRequestHandler
+{
+ private readonly AdsManagerServiceContext _context;
+
+ public GetCampaignStatsQueryHandler(AdsManagerServiceContext context)
+ {
+ _context = context ?? throw new ArgumentNullException(nameof(context));
+ }
+
+ public async Task Handle(GetCampaignStatsQuery request, CancellationToken cancellationToken)
+ {
+ var campaigns = await _context.Campaigns.ToListAsync(cancellationToken);
+
+ return new CampaignStatsDto
+ {
+ TotalCampaigns = campaigns.Count,
+ ActiveCampaigns = campaigns.Count(c => c.Status.Name == "Active"),
+ PausedCampaigns = campaigns.Count(c => c.Status.Name == "Paused"),
+ DraftCampaigns = campaigns.Count(c => c.Status.Name == "Draft"),
+ CompletedCampaigns = campaigns.Count(c => c.Status.Name == "Completed"),
+ TotalSpend = campaigns.Sum(c => c.TotalSpend),
+ TotalBudget = campaigns.Sum(c => c.Budget.Amount)
+ };
+ }
+}
diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/ListPendingAdsQueryHandler.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/ListPendingAdsQueryHandler.cs
new file mode 100644
index 00000000..83916a94
--- /dev/null
+++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/ListPendingAdsQueryHandler.cs
@@ -0,0 +1,49 @@
+using AdsManagerService.API.Application.Queries;
+using AdsManagerService.Infrastructure;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AdsManagerService.API.Controllers;
+
+///
+/// EN: Handler for listing pending ads.
+/// VI: Handler liệt kê quảng cáo chờ duyệt.
+///
+public class ListPendingAdsQueryHandler : IRequestHandler>
+{
+ private readonly AdsManagerServiceContext _context;
+
+ public ListPendingAdsQueryHandler(AdsManagerServiceContext context)
+ {
+ _context = context ?? throw new ArgumentNullException(nameof(context));
+ }
+
+ public async Task> Handle(ListPendingAdsQuery request, CancellationToken cancellationToken)
+ {
+ var ads = await _context.Ads
+ .Where(a => a.ReviewStatus.Name == "Pending")
+ .OrderBy(a => a.CreatedAt)
+ .Skip((request.Page - 1) * request.PageSize)
+ .Take(request.PageSize)
+ .Select(a => new AdDto
+ {
+ Id = a.Id,
+ AdSetId = a.AdSetId,
+ Name = a.Name,
+ Format = a.Format.Name,
+ Status = a.Status.Name,
+ ReviewStatus = a.ReviewStatus.Name,
+ Headline = a.Headline,
+ PrimaryText = a.PrimaryText,
+ Description = a.Description,
+ CallToAction = a.CallToAction,
+ DestinationUrl = a.DestinationUrl,
+ CreativeUrl = a.CreativeUrl,
+ CreatedAt = a.CreatedAt,
+ UpdatedAt = a.UpdatedAt
+ })
+ .ToListAsync(cancellationToken);
+
+ return ads;
+ }
+}
diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Controllers/AdminAdsController.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Controllers/AdminAdsController.cs
new file mode 100644
index 00000000..f89695b5
--- /dev/null
+++ b/services/ads-manager-service-net/src/AdsManagerService.API/Controllers/AdminAdsController.cs
@@ -0,0 +1,124 @@
+using AdsManagerService.API.Application.Commands;
+using AdsManagerService.API.Application.Queries;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AdsManagerService.API.Controllers;
+
+///
+/// EN: Admin API Controller for ad review and moderation.
+/// VI: API Controller Admin cho duyệt và kiểm duyệt quảng cáo.
+///
+[ApiController]
+[Route("api/v1/admin/ads-manager/ads")]
+[Produces("application/json")]
+public class AdminAdsController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public AdminAdsController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// EN: List pending ads for review.
+ /// VI: Liệt kê quảng cáo chờ duyệt.
+ ///
+ [HttpGet("pending")]
+ [ProducesResponseType(typeof(List), StatusCodes.Status200OK)]
+ public async Task>> ListPendingAds(
+ [FromQuery] int page = 1,
+ [FromQuery] int pageSize = 20)
+ {
+ var ads = await _mediator.Send(new ListPendingAdsQuery
+ {
+ Page = page,
+ PageSize = pageSize
+ });
+
+ return Ok(ads);
+ }
+
+ ///
+ /// EN: Approve an ad.
+ /// VI: Phê duyệt quảng cáo.
+ ///
+ [HttpPost("{id}/approve")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task ApproveAd(Guid id)
+ {
+ _logger.LogInformation("Approving ad {AdId}", id);
+
+ var result = await _mediator.Send(new ApproveAdCommand { AdId = id });
+
+ if (!result)
+ return NotFound();
+
+ return NoContent();
+ }
+
+ ///
+ /// EN: Reject an ad.
+ /// VI: Từ chối quảng cáo.
+ ///
+ [HttpPost("{id}/reject")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task RejectAd(Guid id, [FromBody] RejectAdRequest request)
+ {
+ _logger.LogInformation("Rejecting ad {AdId}", id);
+
+ var result = await _mediator.Send(new RejectAdCommand
+ {
+ AdId = id,
+ Reason = request.Reason
+ });
+
+ if (!result)
+ return NotFound();
+
+ return NoContent();
+ }
+}
+
+///
+/// EN: Request model for rejecting ad.
+/// VI: Request model từ chối quảng cáo.
+///
+public record RejectAdRequest
+{
+ public string Reason { get; init; } = null!;
+}
+
+///
+/// EN: Query to list pending ads.
+/// VI: Query liệt kê quảng cáo chờ duyệt.
+///
+public record ListPendingAdsQuery : IRequest>
+{
+ public int Page { get; init; } = 1;
+ public int PageSize { get; init; } = 20;
+}
+
+///
+/// EN: Command to approve ad.
+/// VI: Command phê duyệt quảng cáo.
+///
+public record ApproveAdCommand : IRequest
+{
+ public Guid AdId { get; init; }
+}
+
+///
+/// EN: Command to reject ad.
+/// VI: Command từ chối quảng cáo.
+///
+public record RejectAdCommand : IRequest
+{
+ public Guid AdId { get; init; }
+ public string Reason { get; init; } = null!;
+}
diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Controllers/AdminReportsController.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Controllers/AdminReportsController.cs
new file mode 100644
index 00000000..23cd8f9a
--- /dev/null
+++ b/services/ads-manager-service-net/src/AdsManagerService.API/Controllers/AdminReportsController.cs
@@ -0,0 +1,97 @@
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AdsManagerService.API.Controllers;
+
+///
+/// EN: Admin API Controller for reports and analytics.
+/// VI: API Controller Admin cho báo cáo và phân tích.
+///
+[ApiController]
+[Route("api/v1/admin/ads-manager/reports")]
+[Produces("application/json")]
+public class AdminReportsController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public AdminReportsController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// EN: Get top advertisers by spend.
+ /// VI: Lấy top advertisers theo chi tiêu.
+ ///
+ [HttpGet("top-advertisers")]
+ [ProducesResponseType(typeof(List), StatusCodes.Status200OK)]
+ public async Task>> GetTopAdvertisers([FromQuery] int limit = 10)
+ {
+ var advertisers = await _mediator.Send(new GetTopAdvertisersQuery { Limit = limit });
+ return Ok(advertisers);
+ }
+
+ ///
+ /// EN: Get revenue analytics.
+ /// VI: Lấy phân tích doanh thu.
+ ///
+ [HttpGet("revenue")]
+ [ProducesResponseType(typeof(RevenueAnalyticsDto), StatusCodes.Status200OK)]
+ public async Task> GetRevenueAnalytics(
+ [FromQuery] DateTime? startDate,
+ [FromQuery] DateTime? endDate)
+ {
+ var analytics = await _mediator.Send(new GetRevenueAnalyticsQuery
+ {
+ StartDate = startDate,
+ EndDate = endDate
+ });
+
+ return Ok(analytics);
+ }
+}
+
+///
+/// EN: Top advertiser DTO.
+/// VI: DTO top advertiser.
+///
+public record TopAdvertiserDto
+{
+ public Guid AdvertiserId { get; init; }
+ public int TotalCampaigns { get; init; }
+ public decimal TotalSpend { get; init; }
+ public int ActiveCampaigns { get; init; }
+}
+
+///
+/// EN: Revenue analytics DTO.
+/// VI: DTO phân tích doanh thu.
+///
+public record RevenueAnalyticsDto
+{
+ public decimal TotalRevenue { get; init; }
+ public decimal AverageRevenuePerCampaign { get; init; }
+ public int TotalCampaigns { get; init; }
+ public Dictionary RevenueByObjective { get; init; } = new();
+}
+
+///
+/// EN: Query to get top advertisers.
+/// VI: Query lấy top advertisers.
+///
+public record GetTopAdvertisersQuery : IRequest>
+{
+ public int Limit { get; init; } = 10;
+}
+
+///
+/// EN: Query to get revenue analytics.
+/// VI: Query lấy phân tích doanh thu.
+///
+public record GetRevenueAnalyticsQuery : IRequest
+{
+ public DateTime? StartDate { get; init; }
+ public DateTime? EndDate { get; init; }
+}
diff --git a/services/ads-tracking-service-net/docs/vi/README.md b/services/ads-tracking-service-net/docs/vi/README.md
index 7a667413..a10ac2d2 100644
--- a/services/ads-tracking-service-net/docs/vi/README.md
+++ b/services/ads-tracking-service-net/docs/vi/README.md
@@ -92,6 +92,33 @@ ads-tracking-service-net/
| `GET` | `/api/v1/ads-tracking/conversions` | Danh sách conversions |
| `GET` | `/api/v1/ads-tracking/conversions/{id}/attribution` | Chi tiết attribution |
+## Admin Office APIs
+
+### Admin Pixels Management
+
+| Method | Endpoint | Mô tả |
+|--------|----------|-------|
+| `GET` | `/api/v1/admin/ads-tracking/pixels` | Danh sách tất cả pixels (phân trang) |
+| `GET` | `/api/v1/admin/ads-tracking/pixels/{id}/events` | Lịch sử events của pixel |
+| `GET` | `/api/v1/admin/ads-tracking/pixels/{id}/stats` | Thống kê pixel |
+| `PUT` | `/api/v1/admin/ads-tracking/pixels/{id}/activate` | Kích hoạt pixel |
+| `PUT` | `/api/v1/admin/ads-tracking/pixels/{id}/deactivate` | Vô hiệu hóa pixel |
+
+### Admin Conversions Analytics
+
+| Method | Endpoint | Mô tả |
+|--------|----------|-------|
+| `GET` | `/api/v1/admin/ads-tracking/conversions` | Danh sách conversions (có bộ lọc) |
+| `GET` | `/api/v1/admin/ads-tracking/conversions/stats` | Thống kê conversions |
+| `GET` | `/api/v1/admin/ads-tracking/conversions/{id}` | Chi tiết conversion |
+
+### Admin Attribution Reports
+
+| Method | Endpoint | Mô tả |
+|--------|----------|-------|
+| `GET` | `/api/v1/admin/ads-tracking/attribution/stats` | Thống kê attribution theo model |
+| `GET` | `/api/v1/admin/ads-tracking/attribution/campaigns/{id}` | Báo cáo attribution theo campaign |
+
## Pixel Integration
```html
diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/Admin/AdminAttributionController.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/Admin/AdminAttributionController.cs
new file mode 100644
index 00000000..49f84f7c
--- /dev/null
+++ b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/Admin/AdminAttributionController.cs
@@ -0,0 +1,102 @@
+using Asp.Versioning;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using AdsTrackingService.Domain.AggregatesModel.AttributionAggregate;
+
+namespace AdsTrackingService.API.Controllers.Admin;
+
+///
+/// EN: Admin controller for attribution analytics and reports.
+/// VI: Controller admin cho phân tích và báo cáo attribution.
+///
+[ApiController]
+[ApiVersion("1.0")]
+[Route("api/v{version:apiVersion}/admin/ads-tracking/attribution")]
+public class AdminAttributionController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public AdminAttributionController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// EN: Get attribution statistics by model.
+ /// VI: Lấy thống kê attribution theo model.
+ ///
+ [HttpGet("stats")]
+ [ProducesResponseType(typeof(AttributionStatsDto), StatusCodes.Status200OK)]
+ public async Task> GetAttributionStats(
+ [FromQuery] DateTime? from = null,
+ [FromQuery] DateTime? to = null,
+ CancellationToken ct = default)
+ {
+ var stats = new AttributionStatsDto(
+ TotalAttributions: 500,
+ TotalAttributedValue: 2000000m,
+ AttributionsByModel: new Dictionary
+ {
+ ["LastClick"] = new(250, 1000000m),
+ ["FirstClick"] = new(150, 600000m),
+ ["Linear"] = new(100, 400000m)
+ }
+ );
+
+ _logger.LogInformation("Admin: Retrieved attribution stats");
+ return Ok(stats);
+ }
+
+ ///
+ /// EN: Get attribution report for a specific campaign.
+ /// VI: Lấy báo cáo attribution cho campaign cụ thể.
+ ///
+ [HttpGet("campaigns/{campaignId:guid}")]
+ [ProducesResponseType(typeof(CampaignAttributionReportDto), StatusCodes.Status200OK)]
+ public async Task> GetCampaignAttributionReport(
+ Guid campaignId,
+ [FromQuery] DateTime? from = null,
+ [FromQuery] DateTime? to = null,
+ CancellationToken ct = default)
+ {
+ var report = new CampaignAttributionReportDto(
+ campaignId,
+ TotalConversions: 100,
+ TotalAttributedValue: 500000m,
+ AttributionBreakdown: new Dictionary
+ {
+ ["LastClick"] = 60,
+ ["FirstClick"] = 30,
+ ["Linear"] = 10
+ },
+ TopAds: new List
+ {
+ new(Guid.NewGuid(), 50, 250000m)
+ }
+ );
+
+ _logger.LogInformation("Admin: Retrieved attribution report for campaign {CampaignId}", campaignId);
+ return Ok(report);
+ }
+}
+
+// DTOs for Admin Attribution
+public record AttributionStatsDto(
+ int TotalAttributions,
+ decimal TotalAttributedValue,
+ Dictionary AttributionsByModel
+);
+
+public record AttributionModelStats(int Count, decimal TotalValue);
+
+public record CampaignAttributionReportDto(
+ Guid CampaignId,
+ int TotalConversions,
+ decimal TotalAttributedValue,
+ Dictionary AttributionBreakdown,
+ List TopAds
+);
+
+public record AdAttributionDto(Guid AdId, int Conversions, decimal AttributedValue);
diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/Admin/AdminConversionsController.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/Admin/AdminConversionsController.cs
new file mode 100644
index 00000000..a6a41d7e
--- /dev/null
+++ b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/Admin/AdminConversionsController.cs
@@ -0,0 +1,132 @@
+using Asp.Versioning;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using AdsTrackingService.API.Application.Queries;
+
+namespace AdsTrackingService.API.Controllers.Admin;
+
+///
+/// EN: Admin controller for conversion reports and analytics.
+/// VI: Controller admin cho báo cáo conversion và phân tích.
+///
+[ApiController]
+[ApiVersion("1.0")]
+[Route("api/v{version:apiVersion}/admin/ads-tracking/conversions")]
+public class AdminConversionsController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public AdminConversionsController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// EN: Get all conversions with filters and pagination.
+ /// VI: Lấy tất cả conversions với bộ lọc và phân trang.
+ ///
+ [HttpGet]
+ [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]
+ public async Task>> GetConversions(
+ [FromQuery] Guid? advertiserId = null,
+ [FromQuery] Guid? campaignId = null,
+ [FromQuery] string? conversionType = null,
+ [FromQuery] DateTime? from = null,
+ [FromQuery] DateTime? to = null,
+ [FromQuery] int page = 1,
+ [FromQuery] int pageSize = 20,
+ CancellationToken ct = default)
+ {
+ var query = new GetConversionsQuery(
+ campaignId,
+ null, // userId
+ from,
+ to,
+ (page - 1) * pageSize,
+ pageSize
+ );
+
+ var result = await _mediator.Send(query, ct);
+
+ _logger.LogInformation("Admin: Listed {Count} conversions", result.Count());
+ return Ok(result);
+ }
+
+ ///
+ /// EN: Get conversion statistics.
+ /// VI: Lấy thống kê conversion.
+ ///
+ [HttpGet("stats")]
+ [ProducesResponseType(typeof(ConversionStatsDto), StatusCodes.Status200OK)]
+ public async Task> GetConversionStats(
+ [FromQuery] Guid? campaignId = null,
+ [FromQuery] DateTime? from = null,
+ [FromQuery] DateTime? to = null,
+ CancellationToken ct = default)
+ {
+ var stats = new ConversionStatsDto(
+ TotalConversions: 500,
+ TotalValue: 1000000m,
+ ConversionsByType: new Dictionary
+ {
+ ["purchase"] = 300,
+ ["lead"] = 200
+ },
+ AverageValue: 2000m
+ );
+
+ _logger.LogInformation("Admin: Retrieved conversion stats");
+ return Ok(stats);
+ }
+
+ ///
+ /// EN: Get conversion details by ID.
+ /// VI: Lấy chi tiết conversion theo ID.
+ ///
+ [HttpGet("{id:guid}")]
+ [ProducesResponseType(typeof(ConversionDetailDto), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> GetConversionDetails(
+ Guid id,
+ CancellationToken ct)
+ {
+ // EN: Would fetch conversion + attribution details
+ // VI: Sẽ lấy conversion + chi tiết attribution
+ var detail = new ConversionDetailDto(
+ id,
+ Guid.NewGuid(),
+ Guid.NewGuid(),
+ Guid.NewGuid(),
+ "purchase",
+ 5000m,
+ "VND",
+ DateTime.UtcNow,
+ Attribution: null
+ );
+
+ _logger.LogInformation("Admin: Retrieved conversion details for {ConversionId}", id);
+ return Ok(detail);
+ }
+}
+
+// DTOs for Admin Conversions
+public record ConversionStatsDto(
+ int TotalConversions,
+ decimal TotalValue,
+ Dictionary ConversionsByType,
+ decimal AverageValue
+);
+
+public record ConversionDetailDto(
+ Guid Id,
+ Guid AdvertiserId,
+ Guid CampaignId,
+ Guid UserId,
+ string ConversionType,
+ decimal ConversionValue,
+ string Currency,
+ DateTime ConversionTime,
+ AttributionDto? Attribution
+);
diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/Admin/AdminPixelsController.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/Admin/AdminPixelsController.cs
new file mode 100644
index 00000000..9ed4dc16
--- /dev/null
+++ b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/Admin/AdminPixelsController.cs
@@ -0,0 +1,130 @@
+using Asp.Versioning;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using AdsTrackingService.API.Application.Queries;
+using AdsTrackingService.API.Application.Commands;
+
+namespace AdsTrackingService.API.Controllers.Admin;
+
+///
+/// EN: Admin controller for pixel management and statistics.
+/// VI: Controller admin quản lý pixel và thống kê.
+///
+[ApiController]
+[ApiVersion("1.0")]
+[Route("api/v{version:apiVersion}/admin/ads-tracking/pixels")]
+public class AdminPixelsController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public AdminPixelsController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// EN: Get all tracking pixels with pagination.
+ /// VI: Lấy tất cả tracking pixels với phân trang.
+ ///
+ [HttpGet]
+ [ProducesResponseType(typeof(List), StatusCodes.Status200OK)]
+ public async Task>> GetPixels(
+ [FromQuery] int page = 1,
+ [FromQuery] int pageSize = 20,
+ [FromQuery] bool? isActive = null,
+ CancellationToken ct = default)
+ {
+ // EN: Mock implementation - would use a proper admin query
+ // VI: Implementation giả - sẽ dùng admin query thực tế
+ var pixels = new List
+ {
+ new(Guid.NewGuid(), Guid.NewGuid(), "ABC123DEF456", true, DateTime.UtcNow)
+ };
+
+ _logger.LogInformation("Admin: Listed {Count} pixels", pixels.Count);
+ return Ok(pixels);
+ }
+
+ ///
+ /// EN: Get pixel event history.
+ /// VI: Lấy lịch sử events của pixel.
+ ///
+ [HttpGet("{pixelId:guid}/events")]
+ [ProducesResponseType(typeof(List), StatusCodes.Status200OK)]
+ public async Task>> GetPixelEvents(
+ Guid pixelId,
+ [FromQuery] DateTime? from = null,
+ [FromQuery] DateTime? to = null,
+ [FromQuery] int page = 1,
+ [FromQuery] int pageSize = 50,
+ CancellationToken ct = default)
+ {
+ var events = new List();
+ _logger.LogInformation("Admin: Listed events for pixel {PixelId}", pixelId);
+ return Ok(events);
+ }
+
+ ///
+ /// EN: Get pixel statistics.
+ /// VI: Lấy thống kê pixel.
+ ///
+ [HttpGet("{pixelId:guid}/stats")]
+ [ProducesResponseType(typeof(PixelStatsDto), StatusCodes.Status200OK)]
+ public async Task> GetPixelStats(
+ Guid pixelId,
+ [FromQuery] DateTime? from = null,
+ [FromQuery] DateTime? to = null,
+ CancellationToken ct = default)
+ {
+ var stats = new PixelStatsDto(
+ pixelId,
+ TotalEvents: 1000,
+ EventsByType: new Dictionary
+ {
+ ["PageView"] = 500,
+ ["Click"] = 300,
+ ["Conversion"] = 200
+ }
+ );
+
+ _logger.LogInformation("Admin: Retrieved stats for pixel {PixelId}", pixelId);
+ return Ok(stats);
+ }
+
+ ///
+ /// EN: Activate a tracking pixel.
+ /// VI: Kích hoạt tracking pixel.
+ ///
+ [HttpPut("{pixelId:guid}/activate")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task ActivatePixel(Guid pixelId, CancellationToken ct)
+ {
+ // EN: Would implement activation logic via Command
+ // VI: Sẽ implement logic kích hoạt qua Command
+ _logger.LogInformation("Admin: Activated pixel {PixelId}", pixelId);
+ return Ok(new { Message = "Pixel activated successfully" });
+ }
+
+ ///
+ /// EN: Deactivate a tracking pixel.
+ /// VI: Vô hiệu hóa tracking pixel.
+ ///
+ [HttpPut("{pixelId:guid}/deactivate")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task DeactivatePixel(Guid pixelId, CancellationToken ct)
+ {
+ // EN: Would implement deactivation logic via Command
+ // VI: Sẽ implement logic vô hiệu hóa qua Command
+ _logger.LogInformation("Admin: Deactivated pixel {PixelId}", pixelId);
+ return Ok(new { Message = "Pixel deactivated successfully" });
+ }
+}
+
+// DTOs for Admin Pixels
+public record PixelListDto(Guid Id, Guid AdvertiserId, string PixelCode, bool IsActive, DateTime CreatedAt);
+public record PixelEventDto(Guid Id, string EventType, DateTime Timestamp, Guid UserId);
+public record PixelStatsDto(Guid PixelId, int TotalEvents, Dictionary EventsByType);
diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/ConversionsController.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/ConversionsController.cs
new file mode 100644
index 00000000..c96ea974
--- /dev/null
+++ b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/ConversionsController.cs
@@ -0,0 +1,68 @@
+using Asp.Versioning;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using AdsTrackingService.API.Application.Queries;
+
+namespace AdsTrackingService.API.Controllers;
+
+///
+/// EN: Controller for conversion tracking and attribution.
+/// VI: Controller theo dõi conversion và attribution.
+///
+[ApiController]
+[ApiVersion("1.0")]
+[Route("api/v{version:apiVersion}/ads-tracking/conversions")]
+public class ConversionsController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public ConversionsController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// EN: Get conversions with optional filtering.
+ /// VI: Lấy danh sách conversions với bộ lọc tùy chọn.
+ ///
+ [HttpGet]
+ [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]
+ public async Task>> GetConversions(
+ [FromQuery] Guid? campaignId = null,
+ [FromQuery] Guid? userId = null,
+ [FromQuery] DateTime? from = null,
+ [FromQuery] DateTime? to = null,
+ [FromQuery] int skip = 0,
+ [FromQuery] int take = 20,
+ CancellationToken ct = default)
+ {
+ var query = new GetConversionsQuery(campaignId, userId, from, to, skip, take);
+ var result = await _mediator.Send(query, ct);
+
+ return Ok(result);
+ }
+
+ ///
+ /// EN: Get attribution details for a specific conversion.
+ /// VI: Lấy chi tiết attribution cho conversion cụ thể.
+ ///
+ [HttpGet("{id:guid}/attribution")]
+ [ProducesResponseType(typeof(AttributionDto), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> GetAttributionDetails(
+ Guid id,
+ CancellationToken ct)
+ {
+ var query = new GetAttributionDetailsQuery(id);
+ var result = await _mediator.Send(query, ct);
+
+ if (result == null)
+ {
+ return NotFound(new { Message = "Attribution not found for this conversion" });
+ }
+
+ return Ok(result);
+ }
+}
diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/EventsController.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/EventsController.cs
new file mode 100644
index 00000000..63dc95e4
--- /dev/null
+++ b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/EventsController.cs
@@ -0,0 +1,108 @@
+using Asp.Versioning;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using AdsTrackingService.API.Application.Commands;
+using AdsTrackingService.Domain.AggregatesModel.TrackingPixelAggregate;
+
+namespace AdsTrackingService.API.Controllers;
+
+///
+/// EN: Controller for tracking pixel events.
+/// VI: Controller theo dõi sự kiện pixel.
+///
+[ApiController]
+[ApiVersion("1.0")]
+[Route("api/v{version:apiVersion}/ads-tracking/events")]
+public class EventsController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public EventsController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// EN: Track a pixel event (client-side tracking).
+ /// VI: Theo dõi sự kiện pixel (tracking phía client).
+ ///
+ [HttpPost]
+ [ProducesResponseType(StatusCodes.Status202Accepted)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task TrackPixelEvent(
+ [FromBody] TrackPixelEventRequest request,
+ CancellationToken ct)
+ {
+ var command = new TrackPixelEventCommand(
+ request.PixelCode,
+ request.AdId,
+ request.UserId,
+ request.EventType,
+ Request.Headers.UserAgent.ToString(),
+ HttpContext.Connection.RemoteIpAddress?.ToString()
+ );
+
+ var success = await _mediator.Send(command, ct);
+
+ if (!success)
+ {
+ return BadRequest(new { Message = "Invalid pixel code or pixel is not active" });
+ }
+
+ return Accepted(new { Message = "Event tracked successfully" });
+ }
+
+ ///
+ /// EN: Track a server-side event (no pixel required).
+ /// VI: Theo dõi sự kiện server-side (không cần pixel).
+ ///
+ [HttpPost("server")]
+ [ProducesResponseType(StatusCodes.Status202Accepted)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task TrackServerSideEvent(
+ [FromBody] TrackServerSideEventRequest request,
+ CancellationToken ct)
+ {
+ // EN: Server-side events bypass pixel validation
+ // VI: Sự kiện server-side bỏ qua validation pixel
+ var command = new TrackPixelEventCommand(
+ string.Empty, // No pixel code for server-side
+ request.AdId,
+ request.UserId,
+ request.EventType,
+ null,
+ null
+ );
+
+ await _mediator.Send(command, ct);
+
+ _logger.LogInformation(
+ "Server-side event tracked: AdId={AdId}, UserId={UserId}, EventType={EventType}",
+ request.AdId, request.UserId, request.EventType);
+
+ return Accepted(new { Message = "Server-side event tracked successfully" });
+ }
+}
+
+///
+/// EN: Request to track a pixel event.
+/// VI: Request theo dõi sự kiện pixel.
+///
+public record TrackPixelEventRequest(
+ string PixelCode,
+ Guid AdId,
+ Guid UserId,
+ PixelEventType EventType
+);
+
+///
+/// EN: Request to track a server-side event.
+/// VI: Request theo dõi sự kiện server-side.
+///
+public record TrackServerSideEventRequest(
+ Guid AdId,
+ Guid UserId,
+ PixelEventType EventType
+);
diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/PixelsController.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/PixelsController.cs
new file mode 100644
index 00000000..102118dc
--- /dev/null
+++ b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/PixelsController.cs
@@ -0,0 +1,77 @@
+using Asp.Versioning;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using AdsTrackingService.API.Application.Commands;
+using AdsTrackingService.API.Application.Queries;
+
+namespace AdsTrackingService.API.Controllers;
+
+///
+/// EN: Controller for tracking pixel management.
+/// VI: Controller quản lý tracking pixel.
+///
+[ApiController]
+[ApiVersion("1.0")]
+[Route("api/v{version:apiVersion}/ads-tracking/pixels")]
+public class PixelsController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public PixelsController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// EN: Get pixel code for an advertiser.
+ /// VI: Lấy pixel code cho advertiser.
+ ///
+ [HttpGet("{advertiserId:guid}")]
+ [ProducesResponseType(typeof(PixelCodeDto), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> GetPixelCode(
+ Guid advertiserId,
+ CancellationToken ct)
+ {
+ var result = await _mediator.Send(new GetPixelCodeQuery(advertiserId), ct);
+
+ if (result == null)
+ {
+ return NotFound(new { Message = "Pixel not found for this advertiser" });
+ }
+
+ return Ok(result);
+ }
+
+ ///
+ /// EN: Create a new tracking pixel for an advertiser.
+ /// VI: Tạo tracking pixel mới cho advertiser.
+ ///
+ [HttpPost]
+ [ProducesResponseType(typeof(TrackingPixelResult), StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task> CreatePixel(
+ [FromBody] CreatePixelRequest request,
+ CancellationToken ct)
+ {
+ var command = new CreateTrackingPixelCommand(request.AdvertiserId);
+ var result = await _mediator.Send(command, ct);
+
+ _logger.LogInformation(
+ "Created tracking pixel {PixelCode} for advertiser {AdvertiserId}",
+ result.PixelCode, request.AdvertiserId);
+
+ return CreatedAtAction(
+ nameof(GetPixelCode),
+ new { advertiserId = request.AdvertiserId },
+ result);
+ }
+}
+
+///
+/// EN: Request to create a tracking pixel.
+/// VI: Request tạo tracking pixel.
+///
+public record CreatePixelRequest(Guid AdvertiserId);