diff --git a/server/HoneyBox/src/HoneyBox.Core/Interfaces/IWechatService.cs b/server/HoneyBox/src/HoneyBox.Core/Interfaces/IWechatService.cs index 782edd43..b0a494c7 100644 --- a/server/HoneyBox/src/HoneyBox.Core/Interfaces/IWechatService.cs +++ b/server/HoneyBox/src/HoneyBox.Core/Interfaces/IWechatService.cs @@ -1,4 +1,5 @@ using HoneyBox.Model.Models.Auth; +using HoneyBox.Model.Models.Payment; namespace HoneyBox.Core.Interfaces; @@ -27,4 +28,105 @@ public interface IWechatService /// 小程序AppId(可选,不传则使用默认配置) /// access_token,失败返回null Task GetAccessTokenAsync(string? appId = null); + + /// + /// 创建支付订单(Web支付方式) + /// + /// 支付请求 + /// 支付结果 + Task CreatePayOrderAsync(CreatePayRequest request); +} + +/// +/// 创建支付请求 +/// +public class CreatePayRequest +{ + /// + /// 用户ID + /// + public int UserId { get; set; } + + /// + /// 用户OpenId + /// + public string OpenId { get; set; } = string.Empty; + + /// + /// 支付金额(元) + /// + public decimal Price { get; set; } + + /// + /// 商品标题 + /// + public string Title { get; set; } = string.Empty; + + /// + /// 附加数据(订单类型) + /// + public string Attach { get; set; } = string.Empty; + + /// + /// 订单号前缀 + /// + public string Prefix { get; set; } = "MH_"; +} + +/// +/// 创建支付结果 +/// +public class CreatePayResult +{ + /// + /// 状态:1=成功,0=失败 + /// + public int Status { get; set; } + + /// + /// 错误消息 + /// + public string? Message { get; set; } + + /// + /// 订单号 + /// + public string OrderNo { get; set; } = string.Empty; + + /// + /// 支付参数(返回给前端) + /// + public WebPayParams? Res { get; set; } +} + +/// +/// Web支付参数 +/// +public class WebPayParams +{ + /// + /// 支付数据 + /// + public WebPayData? Data { get; set; } + + /// + /// 请求支付URL + /// + public string RequestPay { get; set; } = string.Empty; + + /// + /// 提示信息 + /// + public string Tips { get; set; } = string.Empty; +} + +/// +/// Web支付数据 +/// +public class WebPayData +{ + /// + /// 订单号 + /// + public string OrderNum { get; set; } = string.Empty; } diff --git a/server/HoneyBox/src/HoneyBox.Core/Services/WarehouseService.cs b/server/HoneyBox/src/HoneyBox.Core/Services/WarehouseService.cs index d821f832..4ef31810 100644 --- a/server/HoneyBox/src/HoneyBox.Core/Services/WarehouseService.cs +++ b/server/HoneyBox/src/HoneyBox.Core/Services/WarehouseService.cs @@ -16,15 +16,18 @@ public class WarehouseService : IWarehouseService private readonly HoneyBoxDbContext _dbContext; private readonly ILogger _logger; private readonly ILogisticsService _logisticsService; + private readonly IWechatService _wechatService; public WarehouseService( HoneyBoxDbContext dbContext, ILogger logger, - ILogisticsService logisticsService) + ILogisticsService logisticsService, + IWechatService wechatService) { _dbContext = dbContext; _logger = logger; _logisticsService = logisticsService; + _wechatService = wechatService; } #region 仓库查询 @@ -1099,13 +1102,55 @@ public class WarehouseService : IWarehouseService SendResultDto result; if (freePost > count && postMoney > 0) { - // 需要支付运费,返回支付参数 - // 注意:实际支付参数需要调用微信支付服务生成 + // 需要支付运费,调用微信支付服务生成支付参数 + // 获取用户信息 + var user = await _dbContext.Users.FindAsync(userId); + if (user == null) + { + throw new InvalidOperationException("用户不存在"); + } + + var payRequest = new CreatePayRequest + { + UserId = userId, + OpenId = user.OpenId, + Price = actualFreight, + Title = $"背包发货{count}件", + Attach = "order_list_send", + Prefix = "FH_" + }; + + var payResult = await _wechatService.CreatePayOrderAsync(payRequest); + + if (payResult.Status != 1) + { + throw new InvalidOperationException(payResult.Message ?? "创建支付订单失败"); + } + + // 更新发货记录的订单号为支付订单号 + sendRecord.SendNum = payResult.OrderNo; + await _dbContext.SaveChangesAsync(); + + // 同时更新 OrderItems 的 SendNum + await _dbContext.OrderItems + .Where(o => o.SendNum == sendNum && o.UserId == userId) + .ExecuteUpdateAsync(s => s + .SetProperty(o => o.SendNum, payResult.OrderNo) + .SetProperty(o => o.UpdatedAt, DateTime.UtcNow)); + result = new SendResultDto { Status = 1, // 需要支付 - OrderNo = sendNum, - Res = null // 实际项目中需要调用WechatService生成支付参数 + OrderNo = payResult.OrderNo, + Res = payResult.Res != null ? new WebPayResultDto + { + Data = new WebPayDataDto + { + OrderNum = payResult.OrderNo + }, + RequestPay = payResult.Res.RequestPay, + Tips = payResult.Res.Tips + } : null }; } else diff --git a/server/HoneyBox/src/HoneyBox.Core/Services/WechatService.cs b/server/HoneyBox/src/HoneyBox.Core/Services/WechatService.cs index be13b692..2a6b3ea0 100644 --- a/server/HoneyBox/src/HoneyBox.Core/Services/WechatService.cs +++ b/server/HoneyBox/src/HoneyBox.Core/Services/WechatService.cs @@ -1,5 +1,7 @@ using System.Text.Json; using HoneyBox.Core.Interfaces; +using HoneyBox.Model.Data; +using HoneyBox.Model.Entities; using HoneyBox.Model.Models.Auth; using HoneyBox.Model.Models.Payment; using Microsoft.Extensions.Logging; @@ -17,6 +19,7 @@ public class WechatService : IWechatService private readonly WechatSettings _wechatSettings; private readonly WechatPaySettings _wechatPaySettings; private readonly IRedisService _redisService; + private readonly HoneyBoxDbContext _dbContext; // 微信API端点 private const string WechatCodeToSessionUrl = "https://api.weixin.qq.com/sns/jscode2session"; @@ -31,13 +34,15 @@ public class WechatService : IWechatService ILogger logger, WechatSettings wechatSettings, IOptions wechatPaySettings, - IRedisService redisService) + IRedisService redisService, + HoneyBoxDbContext dbContext) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _wechatSettings = wechatSettings ?? throw new ArgumentNullException(nameof(wechatSettings)); _wechatPaySettings = wechatPaySettings?.Value ?? throw new ArgumentNullException(nameof(wechatPaySettings)); _redisService = redisService ?? throw new ArgumentNullException(nameof(redisService)); + _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); } /// @@ -394,4 +399,104 @@ public class WechatService : IWechatService _logger.LogWarning("未找到AppId {AppId} 的配置,使用默认AppSecret", appId); return _wechatSettings.AppSecret; } + + /// + /// 创建支付订单(Web支付方式) + /// + public async Task CreatePayOrderAsync(CreatePayRequest request) + { + try + { + _logger.LogInformation("[创建支付订单] 开始处理: UserId={UserId}, Price={Price}, Title={Title}, Attach={Attach}", + request.UserId, request.Price, request.Title, request.Attach); + + // 如果金额为0,直接返回成功(免费订单) + if (request.Price <= 0) + { + var freeOrderNo = GenerateOrderNo(request.Prefix, "MON", "H5", "ZFB"); + _logger.LogInformation("[创建支付订单] 免费订单,直接返回成功: OrderNo={OrderNo}", freeOrderNo); + return new CreatePayResult + { + Status = 1, + OrderNo = freeOrderNo, + Res = null + }; + } + + // 生成订单号 + var orderNo = GenerateOrderNo(request.Prefix, "ZFA", "H5", "ZFB"); + + // 截取标题(最多30个字符) + var title = request.Title.Length > 30 ? request.Title.Substring(0, 30) : request.Title; + + // 构建扩展数据 + var extend = new Dictionary + { + { "orderType", request.Attach } + }; + var extendStr = JsonSerializer.Serialize(extend); + + // 创建支付通知记录 + var orderNotify = new OrderNotify + { + OrderNo = orderNo, + NotifyUrl = string.Empty, + NonceStr = string.Empty, + PayTime = DateTime.UtcNow, + PayAmount = request.Price, + Status = 0, // 待处理 + RetryCount = 0, + Attach = request.Attach, + OpenId = request.OpenId, + Extend = extendStr, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + + _dbContext.OrderNotifies.Add(orderNotify); + await _dbContext.SaveChangesAsync(); + + _logger.LogInformation("[创建支付订单] 订单记录已创建: OrderNo={OrderNo}, NotifyId={NotifyId}", + orderNo, orderNotify.Id); + + // 构建Web支付参数 + var webPayParams = new WebPayParams + { + Data = new WebPayData + { + OrderNum = orderNo + }, + RequestPay = "/api/send_web_pay_order", + Tips = "您即将进入客服聊天界面完成支付,也可前往「我的」页面下载官方APP,享受更便捷的购物及充值服务。" + }; + + return new CreatePayResult + { + Status = 1, + OrderNo = orderNo, + Res = webPayParams + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "[创建支付订单] 创建失败: UserId={UserId}", request.UserId); + return new CreatePayResult + { + Status = 0, + Message = "创建支付订单失败", + OrderNo = string.Empty + }; + } + } + + /// + /// 生成订单号 + /// 格式:前缀(3位) + 商户前缀(3位) + 项目前缀(2位) + 支付类型(3位) + 时间戳 + 随机数 + /// + private string GenerateOrderNo(string prefix, string merchantPrefix, string projectPrefix, string payType) + { + var timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); + var random = new Random().Next(1000, 9999); + return $"{prefix}{merchantPrefix}{projectPrefix}{payType}{timestamp}{random}"; + } } diff --git a/server/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs b/server/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs index 72fd7b38..be4d01e6 100644 --- a/server/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs +++ b/server/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs @@ -29,7 +29,8 @@ public class ServiceModule : Module var wechatSettings = c.Resolve(); var wechatPaySettings = c.Resolve>(); var redisService = c.Resolve(); - return new WechatService(httpClientFactory.CreateClient(), logger, wechatSettings, wechatPaySettings, redisService); + var dbContext = c.Resolve(); + return new WechatService(httpClientFactory.CreateClient(), logger, wechatSettings, wechatPaySettings, redisService, dbContext); }).As().InstancePerLifetimeScope(); // 注册 IP 地理位置服务 @@ -218,7 +219,8 @@ public class ServiceModule : Module var dbContext = c.Resolve(); var logger = c.Resolve>(); var logisticsService = c.Resolve(); - return new WarehouseService(dbContext, logger, logisticsService); + var wechatService = c.Resolve(); + return new WarehouseService(dbContext, logger, logisticsService, wechatService); }).As().InstancePerLifetimeScope(); // ========== 支付系统服务注册 ========== diff --git a/server/HoneyBox/src/HoneyBox.Model/Models/Order/WarehouseModels.cs b/server/HoneyBox/src/HoneyBox.Model/Models/Order/WarehouseModels.cs index 4e51e966..c7b5c75b 100644 --- a/server/HoneyBox/src/HoneyBox.Model/Models/Order/WarehouseModels.cs +++ b/server/HoneyBox/src/HoneyBox.Model/Models/Order/WarehouseModels.cs @@ -473,10 +473,46 @@ public class SendResultDto public string OrderNo { get; set; } = string.Empty; /// - /// 微信支付参数(需要支付运费时返回) + /// 支付参数(需要支付运费时返回,支持Web支付和微信原生支付) /// [JsonPropertyName("res")] - public WechatPayParamsDto? Res { get; set; } + public WebPayResultDto? Res { get; set; } +} + +/// +/// Web支付结果DTO(兼容前端支付流程) +/// +public class WebPayResultDto +{ + /// + /// 支付数据(包含订单号) + /// + [JsonPropertyName("data")] + public WebPayDataDto? Data { get; set; } + + /// + /// 支付请求URL + /// + [JsonPropertyName("requestPay")] + public string RequestPay { get; set; } = string.Empty; + + /// + /// 提示信息 + /// + [JsonPropertyName("tips")] + public string Tips { get; set; } = string.Empty; +} + +/// +/// Web支付数据DTO +/// +public class WebPayDataDto +{ + /// + /// 订单号 + /// + [JsonPropertyName("order_num")] + public string OrderNum { get; set; } = string.Empty; } ///