feat: add coupon checkout support

This commit is contained in:
xiong
2026-07-26 20:04:24 +08:00
parent a4621df728
commit 7139da766a
6 changed files with 673 additions and 9 deletions

View File

@@ -22,6 +22,8 @@ public sealed class CreateCommerceOrderDto
[StringLength(100)]
public string? CouponCode { get; set; }
public Guid? CouponRedemptionId { get; set; }
public CreateCommerceOrderCommand ToCommand()
{
return new CreateCommerceOrderCommand(
@@ -30,7 +32,8 @@ public sealed class CreateCommerceOrderDto
PayMethod,
PayProvider,
RegionId,
CouponCode);
CouponCode,
CouponRedemptionId);
}
}
@@ -77,3 +80,50 @@ public sealed class CreateCommercePaymentDto
QuitUrl);
}
}
public sealed class CommerceCouponQueryDto
{
[Range(1, 100)]
public int? Limit { get; set; }
[StringLength(32)]
public string? Status { get; set; }
public CommerceCouponQuery ToQuery()
{
return new CommerceCouponQuery(Limit, Status);
}
}
public sealed class ClaimCommerceCouponDto
{
[Required]
[StringLength(100)]
public string CouponCode { get; set; } = string.Empty;
public ClaimCommerceCouponCommand ToCommand()
{
return new ClaimCommerceCouponCommand(CouponCode);
}
}
public sealed class CheckCommerceCouponDto
{
[StringLength(100)]
public string? CouponCode { get; set; }
public Guid? CouponRedemptionId { get; set; }
[Required]
public Guid PlanId { get; set; }
[Range(1, 99)]
public int Quantity { get; set; } = 1;
public Guid? RegionId { get; set; }
public CheckCommerceCouponCommand ToCommand()
{
return new CheckCommerceCouponCommand(CouponCode, CouponRedemptionId, PlanId, Quantity, RegionId);
}
}

View File

@@ -81,6 +81,45 @@ public sealed class CommerceController(
cancellationToken));
}
[HttpPost("coupons/claim")]
[EndpointSummary("领取优惠券")]
[ProducesResponseType<CommerceCouponItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<CommerceCouponItem>> ClaimCoupon(
ClaimCommerceCouponDto request,
CancellationToken cancellationToken)
{
return Ok(await commerceService.ClaimCouponAsync(
ResolveActor(),
request.ToCommand(),
cancellationToken));
}
[HttpGet("coupons")]
[EndpointSummary("查询当前用户优惠券")]
[ProducesResponseType<CommerceCouponList>(StatusCodes.Status200OK)]
public async Task<ActionResult<CommerceCouponList>> Coupons(
[FromQuery] CommerceCouponQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await commerceService.GetCouponsAsync(
ResolveActor(),
query.ToQuery(),
cancellationToken));
}
[HttpPost("coupons/check")]
[EndpointSummary("校验优惠券并预览订单金额")]
[ProducesResponseType<CommerceCouponCheckResult>(StatusCodes.Status200OK)]
public async Task<ActionResult<CommerceCouponCheckResult>> CheckCoupon(
CheckCommerceCouponDto request,
CancellationToken cancellationToken)
{
return Ok(await commerceService.CheckCouponAsync(
ResolveActor(),
request.ToCommand(),
cancellationToken));
}
[AllowAnonymous]
[HttpPost("payments/notify/wechat-pay")]
[EndpointSummary("微信支付回调")]

View File

@@ -347,9 +347,11 @@ public sealed class ExceptionHandlingMiddleware(
{
"commerce_access_denied" or "tenant_access_denied" => StatusCodes.Status403Forbidden,
"tenant_admin_access_denied" => StatusCodes.Status403Forbidden,
"order_not_found" or "svip_plan_not_found" or "region_not_found" or "activation_code_not_found" => StatusCodes.Status404NotFound,
"order_not_found" or "svip_plan_not_found" or "region_not_found" or "activation_code_not_found" or
"coupon_not_found" or "coupon_redemption_not_found" => StatusCodes.Status404NotFound,
"payment_provider_not_configured" or "payment_secret_not_configured" => StatusCodes.Status503ServiceUnavailable,
"order_status_invalid" or "activation_code_used" or "payment_amount_mismatch" => StatusCodes.Status409Conflict,
"order_status_invalid" or "activation_code_used" or "payment_amount_mismatch" or
"coupon_usage_limit_reached" or "coupon_redemption_status_invalid" => StatusCodes.Status409Conflict,
_ => StatusCodes.Status400BadRequest
};
}