diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Marketing/CouponContracts.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Marketing/CouponContracts.cs
new file mode 100644
index 0000000..86f2804
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Marketing/CouponContracts.cs
@@ -0,0 +1,425 @@
+namespace TakeoutSaaS.TenantApi.Contracts.Marketing;
+
+///
+/// 优惠券列表请求。
+///
+public sealed class CouponListRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 关键字。
+ ///
+ public string? Keyword { get; set; }
+
+ ///
+ /// 状态筛选(ongoing/upcoming/ended/disabled)。
+ ///
+ public string? Status { get; set; }
+
+ ///
+ /// 券类型筛选(amount_off/discount/free_delivery)。
+ ///
+ public string? CouponType { get; set; }
+
+ ///
+ /// 页码。
+ ///
+ public int Page { get; set; } = 1;
+
+ ///
+ /// 每页条数。
+ ///
+ public int PageSize { get; set; } = 10;
+}
+
+///
+/// 优惠券详情请求。
+///
+public sealed class CouponDetailRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 优惠券 ID。
+ ///
+ public string CouponId { get; set; } = string.Empty;
+}
+
+///
+/// 保存优惠券请求。
+///
+public sealed class SaveCouponRequest
+{
+ ///
+ /// 门店 ID(当前操作上下文)。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 优惠券 ID(编辑时传)。
+ ///
+ public string? Id { get; set; }
+
+ ///
+ /// 券名称。
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 券类型(amount_off/discount/free_delivery)。
+ ///
+ public string CouponType { get; set; } = "amount_off";
+
+ ///
+ /// 面值或折扣值。
+ ///
+ public decimal Value { get; set; }
+
+ ///
+ /// 使用门槛。
+ ///
+ public decimal? MinimumSpend { get; set; }
+
+ ///
+ /// 发放总量。
+ ///
+ public int TotalQuantity { get; set; }
+
+ ///
+ /// 每人限领。
+ ///
+ public int? PerUserLimit { get; set; }
+
+ ///
+ /// 有效期类型(fixed/days)。
+ ///
+ public string ValidityType { get; set; } = "fixed";
+
+ ///
+ /// 固定有效期开始。
+ ///
+ public DateTime? ValidFrom { get; set; }
+
+ ///
+ /// 固定有效期结束。
+ ///
+ public DateTime? ValidTo { get; set; }
+
+ ///
+ /// 领取后有效天数。
+ ///
+ public int? RelativeValidDays { get; set; }
+
+ ///
+ /// 适用渠道(delivery/pickup/dine_in)。
+ ///
+ public List? Channels { get; set; }
+
+ ///
+ /// 门店范围模式(all/stores)。
+ ///
+ public string StoreScopeMode { get; set; } = "stores";
+
+ ///
+ /// 门店范围 ID 集合(stores 模式必传)。
+ ///
+ public List? StoreIds { get; set; }
+
+ ///
+ /// 状态(enabled/disabled)。
+ ///
+ public string Status { get; set; } = "enabled";
+}
+
+///
+/// 修改优惠券状态请求。
+///
+public sealed class ChangeCouponStatusRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 优惠券 ID。
+ ///
+ public string CouponId { get; set; } = string.Empty;
+
+ ///
+ /// 状态(enabled/disabled)。
+ ///
+ public string Status { get; set; } = "enabled";
+}
+
+///
+/// 删除优惠券请求。
+///
+public sealed class DeleteCouponRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 优惠券 ID。
+ ///
+ public string CouponId { get; set; } = string.Empty;
+}
+
+///
+/// 优惠券列表结果。
+///
+public sealed class CouponListResultResponse
+{
+ ///
+ /// 列表数据。
+ ///
+ public List Items { get; set; } = [];
+
+ ///
+ /// 总条数。
+ ///
+ public int Total { get; set; }
+
+ ///
+ /// 页码。
+ ///
+ public int Page { get; set; }
+
+ ///
+ /// 每页条数。
+ ///
+ public int PageSize { get; set; }
+
+ ///
+ /// 统计信息。
+ ///
+ public CouponStatsResponse Stats { get; set; } = new();
+}
+
+///
+/// 优惠券列表项。
+///
+public sealed class CouponListItemResponse
+{
+ ///
+ /// 优惠券 ID。
+ ///
+ public string Id { get; set; } = string.Empty;
+
+ ///
+ /// 券名称。
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 券类型(amount_off/discount/free_delivery)。
+ ///
+ public string CouponType { get; set; } = "amount_off";
+
+ ///
+ /// 面值或折扣值。
+ ///
+ public decimal Value { get; set; }
+
+ ///
+ /// 使用门槛。
+ ///
+ public decimal? MinimumSpend { get; set; }
+
+ ///
+ /// 固定有效期开始。
+ ///
+ public string? ValidFrom { get; set; }
+
+ ///
+ /// 固定有效期结束。
+ ///
+ public string? ValidTo { get; set; }
+
+ ///
+ /// 领取后有效天数。
+ ///
+ public int? RelativeValidDays { get; set; }
+
+ ///
+ /// 发放总量。
+ ///
+ public int TotalQuantity { get; set; }
+
+ ///
+ /// 已领取数量。
+ ///
+ public int ClaimedQuantity { get; set; }
+
+ ///
+ /// 已核销数量。
+ ///
+ public int RedeemedQuantity { get; set; }
+
+ ///
+ /// 每人限领。
+ ///
+ public int? PerUserLimit { get; set; }
+
+ ///
+ /// 展示状态(ongoing/upcoming/ended/disabled)。
+ ///
+ public string DisplayStatus { get; set; } = "ongoing";
+
+ ///
+ /// 是否弱化展示。
+ ///
+ public bool IsDimmed { get; set; }
+
+ ///
+ /// 门店范围模式(all/stores)。
+ ///
+ public string StoreScopeMode { get; set; } = "stores";
+
+ ///
+ /// 门店 ID 列表。
+ ///
+ public List StoreIds { get; set; } = [];
+
+ ///
+ /// 渠道列表(delivery/pickup/dine_in)。
+ ///
+ public List Channels { get; set; } = [];
+
+ ///
+ /// 更新时间。
+ ///
+ public string UpdatedAt { get; set; } = string.Empty;
+}
+
+///
+/// 优惠券详情。
+///
+public sealed class CouponDetailResponse
+{
+ ///
+ /// 优惠券 ID。
+ ///
+ public string Id { get; set; } = string.Empty;
+
+ ///
+ /// 券名称。
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 券类型(amount_off/discount/free_delivery)。
+ ///
+ public string CouponType { get; set; } = "amount_off";
+
+ ///
+ /// 面值或折扣值。
+ ///
+ public decimal Value { get; set; }
+
+ ///
+ /// 使用门槛。
+ ///
+ public decimal? MinimumSpend { get; set; }
+
+ ///
+ /// 发放总量。
+ ///
+ public int TotalQuantity { get; set; }
+
+ ///
+ /// 已领取数量。
+ ///
+ public int ClaimedQuantity { get; set; }
+
+ ///
+ /// 每人限领。
+ ///
+ public int? PerUserLimit { get; set; }
+
+ ///
+ /// 有效期类型(fixed/days)。
+ ///
+ public string ValidityType { get; set; } = "fixed";
+
+ ///
+ /// 固定有效期开始。
+ ///
+ public string? ValidFrom { get; set; }
+
+ ///
+ /// 固定有效期结束。
+ ///
+ public string? ValidTo { get; set; }
+
+ ///
+ /// 领取后有效天数。
+ ///
+ public int? RelativeValidDays { get; set; }
+
+ ///
+ /// 渠道列表(delivery/pickup/dine_in)。
+ ///
+ public List Channels { get; set; } = [];
+
+ ///
+ /// 门店范围模式(all/stores)。
+ ///
+ public string StoreScopeMode { get; set; } = "stores";
+
+ ///
+ /// 门店 ID 列表。
+ ///
+ public List StoreIds { get; set; } = [];
+
+ ///
+ /// 状态(enabled/disabled)。
+ ///
+ public string Status { get; set; } = "enabled";
+
+ ///
+ /// 更新时间。
+ ///
+ public string UpdatedAt { get; set; } = string.Empty;
+}
+
+///
+/// 优惠券统计响应。
+///
+public sealed class CouponStatsResponse
+{
+ ///
+ /// 优惠券总数。
+ ///
+ public int TotalCount { get; set; }
+
+ ///
+ /// 进行中数量。
+ ///
+ public int OngoingCount { get; set; }
+
+ ///
+ /// 已领取总数。
+ ///
+ public int ClaimedCount { get; set; }
+
+ ///
+ /// 已核销总数。
+ ///
+ public int RedeemedCount { get; set; }
+
+ ///
+ /// 核销率(百分比)。
+ ///
+ public decimal RedeemRate { get; set; }
+}
diff --git a/src/Api/TakeoutSaaS.TenantApi/Controllers/MarketingCouponController.cs b/src/Api/TakeoutSaaS.TenantApi/Controllers/MarketingCouponController.cs
new file mode 100644
index 0000000..c76d45f
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Controllers/MarketingCouponController.cs
@@ -0,0 +1,319 @@
+using System.Globalization;
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using TakeoutSaaS.Application.App.Coupons.Commands;
+using TakeoutSaaS.Application.App.Coupons.Dto;
+using TakeoutSaaS.Application.App.Coupons.Queries;
+using TakeoutSaaS.Application.App.Stores.Services;
+using TakeoutSaaS.Infrastructure.App.Persistence;
+using TakeoutSaaS.Shared.Abstractions.Constants;
+using TakeoutSaaS.Shared.Abstractions.Exceptions;
+using TakeoutSaaS.Shared.Abstractions.Results;
+using TakeoutSaaS.Shared.Web.Api;
+using TakeoutSaaS.TenantApi.Contracts.Marketing;
+
+namespace TakeoutSaaS.TenantApi.Controllers;
+
+///
+/// 营销中心优惠券模板管理。
+///
+[ApiVersion("1.0")]
+[Authorize]
+[Route("api/tenant/v{version:apiVersion}/marketing/coupon")]
+public sealed class MarketingCouponController(
+ IMediator mediator,
+ TakeoutAppDbContext dbContext,
+ StoreContextService storeContextService) : BaseApiController
+{
+ ///
+ /// 获取优惠券列表。
+ ///
+ [HttpGet("list")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> List(
+ [FromQuery] CouponListRequest request,
+ CancellationToken cancellationToken)
+ {
+ // 1. 解析并校验当前门店上下文
+ var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
+ await EnsureStoreAccessibleAsync(storeId, cancellationToken);
+
+ // 2. 调用应用层查询
+ var result = await mediator.Send(new GetCouponTemplateListQuery
+ {
+ StoreId = storeId,
+ Keyword = request.Keyword,
+ Status = request.Status,
+ CouponType = request.CouponType,
+ Page = request.Page,
+ PageSize = request.PageSize
+ }, cancellationToken);
+
+ // 3. 映射响应
+ return ApiResponse.Ok(new CouponListResultResponse
+ {
+ Items = result.Items.Select(MapListItem).ToList(),
+ Total = result.TotalCount,
+ Page = result.Page,
+ PageSize = result.PageSize,
+ Stats = new CouponStatsResponse
+ {
+ TotalCount = result.Stats.TotalCount,
+ OngoingCount = result.Stats.OngoingCount,
+ ClaimedCount = result.Stats.ClaimedCount,
+ RedeemedCount = result.Stats.RedeemedCount,
+ RedeemRate = result.Stats.RedeemRate
+ }
+ });
+ }
+
+ ///
+ /// 获取优惠券详情。
+ ///
+ [HttpGet("detail")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> Detail(
+ [FromQuery] CouponDetailRequest request,
+ CancellationToken cancellationToken)
+ {
+ // 1. 解析并校验门店访问权限
+ var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
+ await EnsureStoreAccessibleAsync(storeId, cancellationToken);
+
+ // 2. 查询详情
+ var result = await mediator.Send(new GetCouponTemplateDetailQuery
+ {
+ StoreId = storeId,
+ TemplateId = StoreApiHelpers.ParseRequiredSnowflake(request.CouponId, nameof(request.CouponId))
+ }, cancellationToken);
+
+ // 3. 处理不存在场景
+ if (result is null)
+ {
+ return ApiResponse.Error(ErrorCodes.NotFound, "优惠券不存在");
+ }
+
+ return ApiResponse.Ok(MapDetail(result));
+ }
+
+ ///
+ /// 保存优惠券模板(新增/编辑)。
+ ///
+ [HttpPost("save")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> Save(
+ [FromBody] SaveCouponRequest request,
+ CancellationToken cancellationToken)
+ {
+ // 1. 解析并校验操作门店
+ var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
+ var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
+ await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, storeId, cancellationToken);
+
+ // 2. 解析适用门店范围
+ var resolvedStoreIds = await ResolveStoreScopeStoreIdsAsync(
+ request.StoreScopeMode,
+ request.StoreIds,
+ tenantId,
+ merchantId,
+ cancellationToken);
+
+ // 3. 调用应用层保存
+ var result = await mediator.Send(new SaveCouponTemplateCommand
+ {
+ StoreId = storeId,
+ TemplateId = StoreApiHelpers.ParseSnowflakeOrNull(request.Id),
+ Name = request.Name,
+ CouponType = request.CouponType,
+ Value = request.Value,
+ MinimumSpend = request.MinimumSpend,
+ TotalQuantity = request.TotalQuantity,
+ PerUserLimit = request.PerUserLimit,
+ ValidityType = request.ValidityType,
+ ValidFrom = request.ValidFrom,
+ ValidTo = request.ValidTo,
+ RelativeValidDays = request.RelativeValidDays,
+ Channels = request.Channels ?? [],
+ StoreScopeMode = NormalizeStoreScopeMode(request.StoreScopeMode),
+ StoreScopeStoreIds = resolvedStoreIds,
+ Status = request.Status
+ }, cancellationToken);
+
+ return ApiResponse.Ok(MapDetail(result));
+ }
+
+ ///
+ /// 修改优惠券状态。
+ ///
+ [HttpPost("status")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> ChangeStatus(
+ [FromBody] ChangeCouponStatusRequest request,
+ CancellationToken cancellationToken)
+ {
+ // 1. 解析并校验门店访问权限
+ var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
+ await EnsureStoreAccessibleAsync(storeId, cancellationToken);
+
+ // 2. 调用应用层修改状态
+ var result = await mediator.Send(new ChangeCouponTemplateStatusCommand
+ {
+ StoreId = storeId,
+ TemplateId = StoreApiHelpers.ParseRequiredSnowflake(request.CouponId, nameof(request.CouponId)),
+ Status = request.Status
+ }, cancellationToken);
+
+ return ApiResponse.Ok(MapDetail(result));
+ }
+
+ ///
+ /// 删除优惠券模板。
+ ///
+ [HttpPost("delete")]
+ [ProducesResponseType(typeof(ApiResponse