diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Member/MemberPointsMallContracts.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Member/MemberPointsMallContracts.cs
new file mode 100644
index 0000000..8d8f0f3
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Member/MemberPointsMallContracts.cs
@@ -0,0 +1,808 @@
+namespace TakeoutSaaS.TenantApi.Contracts.Member;
+
+///
+/// 积分商城规则详情查询请求。
+///
+public sealed class PointMallRuleDetailRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+}
+
+///
+/// 保存积分商城规则请求。
+///
+public sealed class SavePointMallRuleRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 是否启用消费获取。
+ ///
+ public bool IsConsumeRewardEnabled { get; set; }
+
+ ///
+ /// 每消费多少元触发一次积分计算。
+ ///
+ public int ConsumeAmountPerStep { get; set; }
+
+ ///
+ /// 每步获得积分。
+ ///
+ public int ConsumeRewardPointsPerStep { get; set; }
+
+ ///
+ /// 是否启用评价奖励。
+ ///
+ public bool IsReviewRewardEnabled { get; set; }
+
+ ///
+ /// 评价奖励积分。
+ ///
+ public int ReviewRewardPoints { get; set; }
+
+ ///
+ /// 是否启用注册奖励。
+ ///
+ public bool IsRegisterRewardEnabled { get; set; }
+
+ ///
+ /// 注册奖励积分。
+ ///
+ public int RegisterRewardPoints { get; set; }
+
+ ///
+ /// 是否启用签到奖励。
+ ///
+ public bool IsSigninRewardEnabled { get; set; }
+
+ ///
+ /// 签到奖励积分。
+ ///
+ public int SigninRewardPoints { get; set; }
+
+ ///
+ /// 有效期模式(permanent/yearly_clear)。
+ ///
+ public string ExpiryMode { get; set; } = "yearly_clear";
+}
+
+///
+/// 积分商城商品列表查询请求。
+///
+public sealed class PointMallProductListRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 状态(enabled/disabled,可空)。
+ ///
+ public string? Status { get; set; }
+
+ ///
+ /// 关键字。
+ ///
+ public string? Keyword { get; set; }
+}
+
+///
+/// 积分商城商品详情查询请求。
+///
+public sealed class PointMallProductDetailRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 积分商城商品 ID。
+ ///
+ public string PointMallProductId { get; set; } = string.Empty;
+}
+
+///
+/// 保存积分商城商品请求。
+///
+public sealed class SavePointMallProductRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 积分商城商品 ID(编辑时传)。
+ ///
+ public string? PointMallProductId { get; set; }
+
+ ///
+ /// 展示名称。
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 展示图片。
+ ///
+ public string? ImageUrl { get; set; }
+
+ ///
+ /// 兑换类型(product/coupon/physical)。
+ ///
+ public string RedeemType { get; set; } = "product";
+
+ ///
+ /// 关联商品 ID。
+ ///
+ public string? ProductId { get; set; }
+
+ ///
+ /// 关联优惠券模板 ID。
+ ///
+ public string? CouponTemplateId { get; set; }
+
+ ///
+ /// 实物名称。
+ ///
+ public string? PhysicalName { get; set; }
+
+ ///
+ /// 领取方式(store_pickup/delivery)。
+ ///
+ public string? PickupMethod { get; set; }
+
+ ///
+ /// 商品描述。
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 兑换方式(points/mixed)。
+ ///
+ public string ExchangeType { get; set; } = "points";
+
+ ///
+ /// 所需积分。
+ ///
+ public int RequiredPoints { get; set; }
+
+ ///
+ /// 现金部分。
+ ///
+ public decimal CashAmount { get; set; }
+
+ ///
+ /// 库存总量。
+ ///
+ public int StockTotal { get; set; }
+
+ ///
+ /// 每人限兑次数。
+ ///
+ public int? PerMemberLimit { get; set; }
+
+ ///
+ /// 通知渠道(in_app/sms)。
+ ///
+ public List NotifyChannels { get; set; } = [];
+
+ ///
+ /// 状态(enabled/disabled)。
+ ///
+ public string Status { get; set; } = "enabled";
+}
+
+///
+/// 修改积分商城商品状态请求。
+///
+public sealed class ChangePointMallProductStatusRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 积分商城商品 ID。
+ ///
+ public string PointMallProductId { get; set; } = string.Empty;
+
+ ///
+ /// 状态(enabled/disabled)。
+ ///
+ public string Status { get; set; } = "disabled";
+}
+
+///
+/// 删除积分商城商品请求。
+///
+public sealed class DeletePointMallProductRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 积分商城商品 ID。
+ ///
+ public string PointMallProductId { get; set; } = string.Empty;
+}
+
+///
+/// 积分商城兑换记录分页查询请求。
+///
+public sealed class PointMallRecordListRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 兑换类型(product/coupon/physical)。
+ ///
+ public string? RedeemType { get; set; }
+
+ ///
+ /// 状态(pending_pickup/issued/completed/canceled)。
+ ///
+ public string? Status { get; set; }
+
+ ///
+ /// 开始日期(yyyy-MM-dd)。
+ ///
+ public string? StartDate { get; set; }
+
+ ///
+ /// 结束日期(yyyy-MM-dd)。
+ ///
+ public string? EndDate { get; set; }
+
+ ///
+ /// 关键字。
+ ///
+ public string? Keyword { get; set; }
+
+ ///
+ /// 页码。
+ ///
+ public int Page { get; set; } = 1;
+
+ ///
+ /// 每页条数。
+ ///
+ public int PageSize { get; set; } = 10;
+}
+
+///
+/// 积分商城兑换记录详情请求。
+///
+public sealed class PointMallRecordDetailRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 兑换记录 ID。
+ ///
+ public string RecordId { get; set; } = string.Empty;
+}
+
+///
+/// 导出积分商城兑换记录请求。
+///
+public sealed class ExportPointMallRecordRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 兑换类型(product/coupon/physical)。
+ ///
+ public string? RedeemType { get; set; }
+
+ ///
+ /// 状态(pending_pickup/issued/completed/canceled)。
+ ///
+ public string? Status { get; set; }
+
+ ///
+ /// 开始日期(yyyy-MM-dd)。
+ ///
+ public string? StartDate { get; set; }
+
+ ///
+ /// 结束日期(yyyy-MM-dd)。
+ ///
+ public string? EndDate { get; set; }
+
+ ///
+ /// 关键字。
+ ///
+ public string? Keyword { get; set; }
+}
+
+///
+/// 写入积分商城兑换记录请求。
+///
+public sealed class WritePointMallRecordRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 积分商城商品 ID。
+ ///
+ public string PointMallProductId { get; set; } = string.Empty;
+
+ ///
+ /// 会员 ID。
+ ///
+ public string MemberId { get; set; } = string.Empty;
+
+ ///
+ /// 兑换时间(可空,默认当前时间)。
+ ///
+ public DateTime? RedeemedAt { get; set; }
+}
+
+///
+/// 核销积分商城兑换记录请求。
+///
+public sealed class VerifyPointMallRecordRequest
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 兑换记录 ID。
+ ///
+ public string RecordId { get; set; } = string.Empty;
+
+ ///
+ /// 核销方式(scan/manual)。
+ ///
+ public string VerifyMethod { get; set; } = "manual";
+
+ ///
+ /// 核销备注。
+ ///
+ public string? VerifyRemark { get; set; }
+}
+
+///
+/// 积分商城规则响应。
+///
+public sealed class PointMallRuleResponse
+{
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 是否启用消费获取。
+ ///
+ public bool IsConsumeRewardEnabled { get; set; }
+
+ ///
+ /// 每消费多少元触发一次积分计算。
+ ///
+ public int ConsumeAmountPerStep { get; set; }
+
+ ///
+ /// 每步获得积分。
+ ///
+ public int ConsumeRewardPointsPerStep { get; set; }
+
+ ///
+ /// 是否启用评价奖励。
+ ///
+ public bool IsReviewRewardEnabled { get; set; }
+
+ ///
+ /// 评价奖励积分。
+ ///
+ public int ReviewRewardPoints { get; set; }
+
+ ///
+ /// 是否启用注册奖励。
+ ///
+ public bool IsRegisterRewardEnabled { get; set; }
+
+ ///
+ /// 注册奖励积分。
+ ///
+ public int RegisterRewardPoints { get; set; }
+
+ ///
+ /// 是否启用签到奖励。
+ ///
+ public bool IsSigninRewardEnabled { get; set; }
+
+ ///
+ /// 签到奖励积分。
+ ///
+ public int SigninRewardPoints { get; set; }
+
+ ///
+ /// 有效期模式(permanent/yearly_clear)。
+ ///
+ public string ExpiryMode { get; set; } = "yearly_clear";
+}
+
+///
+/// 积分商城规则统计响应。
+///
+public sealed class PointMallRuleStatsResponse
+{
+ ///
+ /// 累计发放积分。
+ ///
+ public int TotalIssuedPoints { get; set; }
+
+ ///
+ /// 已兑换积分。
+ ///
+ public int RedeemedPoints { get; set; }
+
+ ///
+ /// 积分用户。
+ ///
+ public int PointMembers { get; set; }
+
+ ///
+ /// 兑换率(0-100)。
+ ///
+ public decimal RedeemRate { get; set; }
+}
+
+///
+/// 积分商城规则详情响应。
+///
+public sealed class PointMallRuleDetailResultResponse
+{
+ ///
+ /// 规则。
+ ///
+ public PointMallRuleResponse Rule { get; set; } = new();
+
+ ///
+ /// 统计。
+ ///
+ public PointMallRuleStatsResponse Stats { get; set; } = new();
+}
+
+///
+/// 积分商城商品响应。
+///
+public sealed class PointMallProductResponse
+{
+ ///
+ /// 积分商城商品 ID。
+ ///
+ public string PointMallProductId { get; set; } = string.Empty;
+
+ ///
+ /// 门店 ID。
+ ///
+ public string StoreId { get; set; } = string.Empty;
+
+ ///
+ /// 展示名称。
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 展示图片。
+ ///
+ public string? ImageUrl { get; set; }
+
+ ///
+ /// 兑换类型(product/coupon/physical)。
+ ///
+ public string RedeemType { get; set; } = "product";
+
+ ///
+ /// 兑换类型文案。
+ ///
+ public string RedeemTypeText { get; set; } = "商品";
+
+ ///
+ /// 关联商品 ID。
+ ///
+ public string? ProductId { get; set; }
+
+ ///
+ /// 关联优惠券模板 ID。
+ ///
+ public string? CouponTemplateId { get; set; }
+
+ ///
+ /// 实物名称。
+ ///
+ public string? PhysicalName { get; set; }
+
+ ///
+ /// 领取方式(store_pickup/delivery)。
+ ///
+ public string? PickupMethod { get; set; }
+
+ ///
+ /// 商品描述。
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 兑换方式(points/mixed)。
+ ///
+ public string ExchangeType { get; set; } = "points";
+
+ ///
+ /// 所需积分。
+ ///
+ public int RequiredPoints { get; set; }
+
+ ///
+ /// 现金部分。
+ ///
+ public decimal CashAmount { get; set; }
+
+ ///
+ /// 初始库存。
+ ///
+ public int StockTotal { get; set; }
+
+ ///
+ /// 剩余库存。
+ ///
+ public int StockAvailable { get; set; }
+
+ ///
+ /// 已兑换数量。
+ ///
+ public int RedeemedCount { get; set; }
+
+ ///
+ /// 每人限兑次数。
+ ///
+ public int? PerMemberLimit { get; set; }
+
+ ///
+ /// 通知渠道。
+ ///
+ public List NotifyChannels { get; set; } = [];
+
+ ///
+ /// 状态(enabled/disabled)。
+ ///
+ public string Status { get; set; } = "enabled";
+
+ ///
+ /// 状态文案。
+ ///
+ public string StatusText { get; set; } = "上架";
+
+ ///
+ /// 更新时间。
+ ///
+ public string UpdatedAt { get; set; } = string.Empty;
+}
+
+///
+/// 积分商城商品列表响应。
+///
+public sealed class PointMallProductListResultResponse
+{
+ ///
+ /// 列表。
+ ///
+ public List Items { get; set; } = [];
+}
+
+///
+/// 积分商城兑换记录响应。
+///
+public class PointMallRecordResponse
+{
+ ///
+ /// 兑换记录 ID。
+ ///
+ public string RecordId { get; set; } = string.Empty;
+
+ ///
+ /// 兑换单号。
+ ///
+ public string RecordNo { get; set; } = string.Empty;
+
+ ///
+ /// 积分商城商品 ID。
+ ///
+ public string PointMallProductId { get; set; } = string.Empty;
+
+ ///
+ /// 商品名称。
+ ///
+ public string ProductName { get; set; } = string.Empty;
+
+ ///
+ /// 兑换类型(product/coupon/physical)。
+ ///
+ public string RedeemType { get; set; } = "product";
+
+ ///
+ /// 兑换类型文案。
+ ///
+ public string RedeemTypeText { get; set; } = "商品";
+
+ ///
+ /// 兑换方式(points/mixed)。
+ ///
+ public string ExchangeType { get; set; } = "points";
+
+ ///
+ /// 会员 ID。
+ ///
+ public string MemberId { get; set; } = string.Empty;
+
+ ///
+ /// 会员名称。
+ ///
+ public string MemberName { get; set; } = string.Empty;
+
+ ///
+ /// 会员手机号(脱敏)。
+ ///
+ public string MemberMobileMasked { get; set; } = string.Empty;
+
+ ///
+ /// 消耗积分。
+ ///
+ public int UsedPoints { get; set; }
+
+ ///
+ /// 现金部分。
+ ///
+ public decimal CashAmount { get; set; }
+
+ ///
+ /// 状态(pending_pickup/issued/completed/canceled)。
+ ///
+ public string Status { get; set; } = "issued";
+
+ ///
+ /// 状态文案。
+ ///
+ public string StatusText { get; set; } = "已发放";
+
+ ///
+ /// 兑换时间。
+ ///
+ public string RedeemedAt { get; set; } = string.Empty;
+
+ ///
+ /// 发放时间。
+ ///
+ public string? IssuedAt { get; set; }
+
+ ///
+ /// 核销时间。
+ ///
+ public string? VerifiedAt { get; set; }
+}
+
+///
+/// 积分商城兑换记录详情响应。
+///
+public sealed class PointMallRecordDetailResponse : PointMallRecordResponse
+{
+ ///
+ /// 核销方式(scan/manual)。
+ ///
+ public string? VerifyMethod { get; set; }
+
+ ///
+ /// 核销方式文案。
+ ///
+ public string? VerifyMethodText { get; set; }
+
+ ///
+ /// 核销备注。
+ ///
+ public string? VerifyRemark { get; set; }
+
+ ///
+ /// 核销人 ID。
+ ///
+ public string? VerifiedBy { get; set; }
+}
+
+///
+/// 积分商城兑换记录统计响应。
+///
+public sealed class PointMallRecordStatsResponse
+{
+ ///
+ /// 今日兑换。
+ ///
+ public int TodayRedeemCount { get; set; }
+
+ ///
+ /// 待领取实物。
+ ///
+ public int PendingPhysicalCount { get; set; }
+
+ ///
+ /// 本月消耗积分。
+ ///
+ public int CurrentMonthUsedPoints { get; set; }
+}
+
+///
+/// 积分商城兑换记录分页响应。
+///
+public sealed class PointMallRecordListResultResponse
+{
+ ///
+ /// 列表。
+ ///
+ public List Items { get; set; } = [];
+
+ ///
+ /// 页码。
+ ///
+ public int Page { get; set; }
+
+ ///
+ /// 每页条数。
+ ///
+ public int PageSize { get; set; }
+
+ ///
+ /// 总条数。
+ ///
+ public int TotalCount { get; set; }
+
+ ///
+ /// 统计。
+ ///
+ public PointMallRecordStatsResponse Stats { get; set; } = new();
+}
+
+///
+/// 积分商城兑换记录导出响应。
+///
+public sealed class PointMallRecordExportResponse
+{
+ ///
+ /// 文件名。
+ ///
+ public string FileName { get; set; } = string.Empty;
+
+ ///
+ /// Base64 文件内容。
+ ///
+ public string FileContentBase64 { get; set; } = string.Empty;
+
+ ///
+ /// 导出总数。
+ ///
+ public int TotalCount { get; set; }
+}
diff --git a/src/Api/TakeoutSaaS.TenantApi/Controllers/MemberPointsMallController.cs b/src/Api/TakeoutSaaS.TenantApi/Controllers/MemberPointsMallController.cs
new file mode 100644
index 0000000..1647f89
--- /dev/null
+++ b/src/Api/TakeoutSaaS.TenantApi/Controllers/MemberPointsMallController.cs
@@ -0,0 +1,526 @@
+using System.Globalization;
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using TakeoutSaaS.Application.App.Members.PointsMall.Commands;
+using TakeoutSaaS.Application.App.Members.PointsMall.Dto;
+using TakeoutSaaS.Application.App.Members.PointsMall.Queries;
+using TakeoutSaaS.Application.App.Stores.Services;
+using TakeoutSaaS.Infrastructure.App.Persistence;
+using TakeoutSaaS.Module.Authorization.Attributes;
+using TakeoutSaaS.Shared.Abstractions.Results;
+using TakeoutSaaS.Shared.Web.Api;
+using TakeoutSaaS.TenantApi.Contracts.Member;
+
+namespace TakeoutSaaS.TenantApi.Controllers;
+
+///
+/// 会员中心积分商城管理。
+///
+[ApiVersion("1.0")]
+[Authorize]
+[Route("api/tenant/v{version:apiVersion}/member/points-mall")]
+public sealed class MemberPointsMallController(
+ IMediator mediator,
+ TakeoutAppDbContext dbContext,
+ StoreContextService storeContextService)
+ : BaseApiController
+{
+ private const string ViewPermission = "tenant:member:points-mall:view";
+ private const string ManagePermission = "tenant:member:points-mall:manage";
+
+ ///
+ /// 获取积分规则详情。
+ ///
+ [HttpGet("rule/detail")]
+ [PermissionAuthorize(ViewPermission, ManagePermission)]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> RuleDetail(
+ [FromQuery] PointMallRuleDetailRequest request,
+ CancellationToken cancellationToken)
+ {
+ var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
+ await EnsureStoreAccessibleAsync(storeId, cancellationToken);
+
+ var result = await mediator.Send(new GetPointMallRuleDetailQuery
+ {
+ StoreId = storeId
+ }, cancellationToken);
+
+ return ApiResponse.Ok(new PointMallRuleDetailResultResponse
+ {
+ Rule = MapRule(result.Rule),
+ Stats = new PointMallRuleStatsResponse
+ {
+ TotalIssuedPoints = result.Stats.TotalIssuedPoints,
+ RedeemedPoints = result.Stats.RedeemedPoints,
+ PointMembers = result.Stats.PointMembers,
+ RedeemRate = result.Stats.RedeemRate
+ }
+ });
+ }
+
+ ///
+ /// 保存积分规则。
+ ///
+ [HttpPost("rule/save")]
+ [PermissionAuthorize(ManagePermission)]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> SaveRule(
+ [FromBody] SavePointMallRuleRequest request,
+ CancellationToken cancellationToken)
+ {
+ var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
+ await EnsureStoreAccessibleAsync(storeId, cancellationToken);
+
+ var result = await mediator.Send(new SavePointMallRuleCommand
+ {
+ StoreId = storeId,
+ IsConsumeRewardEnabled = request.IsConsumeRewardEnabled,
+ ConsumeAmountPerStep = request.ConsumeAmountPerStep,
+ ConsumeRewardPointsPerStep = request.ConsumeRewardPointsPerStep,
+ IsReviewRewardEnabled = request.IsReviewRewardEnabled,
+ ReviewRewardPoints = request.ReviewRewardPoints,
+ IsRegisterRewardEnabled = request.IsRegisterRewardEnabled,
+ RegisterRewardPoints = request.RegisterRewardPoints,
+ IsSigninRewardEnabled = request.IsSigninRewardEnabled,
+ SigninRewardPoints = request.SigninRewardPoints,
+ ExpiryMode = request.ExpiryMode
+ }, cancellationToken);
+
+ return ApiResponse.Ok(MapRule(result));
+ }
+
+ ///
+ /// 查询兑换商品列表。
+ ///
+ [HttpGet("product/list")]
+ [PermissionAuthorize(ViewPermission, ManagePermission)]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> ProductList(
+ [FromQuery] PointMallProductListRequest request,
+ CancellationToken cancellationToken)
+ {
+ var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
+ await EnsureStoreAccessibleAsync(storeId, cancellationToken);
+
+ var result = await mediator.Send(new GetPointMallProductListQuery
+ {
+ StoreId = storeId,
+ Status = request.Status,
+ Keyword = request.Keyword
+ }, cancellationToken);
+
+ return ApiResponse.Ok(new PointMallProductListResultResponse
+ {
+ Items = result.Items.Select(MapProduct).ToList()
+ });
+ }
+
+ ///
+ /// 查询兑换商品详情。
+ ///
+ [HttpGet("product/detail")]
+ [PermissionAuthorize(ViewPermission, ManagePermission)]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> ProductDetail(
+ [FromQuery] PointMallProductDetailRequest request,
+ CancellationToken cancellationToken)
+ {
+ var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
+ await EnsureStoreAccessibleAsync(storeId, cancellationToken);
+
+ var result = await mediator.Send(new GetPointMallProductDetailQuery
+ {
+ StoreId = storeId,
+ PointMallProductId = StoreApiHelpers.ParseRequiredSnowflake(request.PointMallProductId, nameof(request.PointMallProductId))
+ }, cancellationToken);
+
+ return ApiResponse.Ok(MapProduct(result));
+ }
+
+ ///
+ /// 保存兑换商品。
+ ///
+ [HttpPost("product/save")]
+ [PermissionAuthorize(ManagePermission)]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> SaveProduct(
+ [FromBody] SavePointMallProductRequest request,
+ CancellationToken cancellationToken)
+ {
+ var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
+ await EnsureStoreAccessibleAsync(storeId, cancellationToken);
+
+ var result = await mediator.Send(new SavePointMallProductCommand
+ {
+ StoreId = storeId,
+ PointMallProductId = StoreApiHelpers.ParseSnowflakeOrNull(request.PointMallProductId),
+ Name = request.Name,
+ ImageUrl = request.ImageUrl,
+ RedeemType = request.RedeemType,
+ ProductId = StoreApiHelpers.ParseSnowflakeOrNull(request.ProductId),
+ CouponTemplateId = StoreApiHelpers.ParseSnowflakeOrNull(request.CouponTemplateId),
+ PhysicalName = request.PhysicalName,
+ PickupMethod = request.PickupMethod,
+ Description = request.Description,
+ ExchangeType = request.ExchangeType,
+ RequiredPoints = request.RequiredPoints,
+ CashAmount = request.CashAmount,
+ StockTotal = request.StockTotal,
+ PerMemberLimit = request.PerMemberLimit,
+ NotifyChannels = request.NotifyChannels,
+ Status = request.Status
+ }, cancellationToken);
+
+ return ApiResponse.Ok(MapProduct(result));
+ }
+
+ ///
+ /// 修改兑换商品状态。
+ ///
+ [HttpPost("product/status")]
+ [PermissionAuthorize(ManagePermission)]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> ChangeProductStatus(
+ [FromBody] ChangePointMallProductStatusRequest request,
+ CancellationToken cancellationToken)
+ {
+ var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
+ await EnsureStoreAccessibleAsync(storeId, cancellationToken);
+
+ var result = await mediator.Send(new ChangePointMallProductStatusCommand
+ {
+ StoreId = storeId,
+ PointMallProductId = StoreApiHelpers.ParseRequiredSnowflake(request.PointMallProductId, nameof(request.PointMallProductId)),
+ Status = request.Status
+ }, cancellationToken);
+
+ return ApiResponse.Ok(MapProduct(result));
+ }
+
+ ///
+ /// 删除兑换商品。
+ ///
+ [HttpPost("product/delete")]
+ [PermissionAuthorize(ManagePermission)]
+ [ProducesResponseType(typeof(ApiResponse