feat: 实现发货运费支付功能
- IWechatService: 添加 CreatePayOrderAsync 方法和相关类型 - WechatService: 实现 Web 支付订单创建,生成订单号并记录到 OrderNotify - WarehouseService: 发货时调用支付服务生成支付参数 - SendResultDto: 修改 Res 属性支持 Web 支付格式 - ServiceModule: 更新依赖注入配置
This commit is contained in:
parent
7f941f1ddc
commit
4fba70467d
|
|
@ -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
|
|||
/// <param name="appId">小程序AppId(可选,不传则使用默认配置)</param>
|
||||
/// <returns>access_token,失败返回null</returns>
|
||||
Task<string?> GetAccessTokenAsync(string? appId = null);
|
||||
|
||||
/// <summary>
|
||||
/// 创建支付订单(Web支付方式)
|
||||
/// </summary>
|
||||
/// <param name="request">支付请求</param>
|
||||
/// <returns>支付结果</returns>
|
||||
Task<CreatePayResult> CreatePayOrderAsync(CreatePayRequest request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建支付请求
|
||||
/// </summary>
|
||||
public class CreatePayRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public int UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户OpenId
|
||||
/// </summary>
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 支付金额(元)
|
||||
/// </summary>
|
||||
public decimal Price { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品标题
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 附加数据(订单类型)
|
||||
/// </summary>
|
||||
public string Attach { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 订单号前缀
|
||||
/// </summary>
|
||||
public string Prefix { get; set; } = "MH_";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建支付结果
|
||||
/// </summary>
|
||||
public class CreatePayResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态:1=成功,0=失败
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误消息
|
||||
/// </summary>
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单号
|
||||
/// </summary>
|
||||
public string OrderNo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 支付参数(返回给前端)
|
||||
/// </summary>
|
||||
public WebPayParams? Res { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Web支付参数
|
||||
/// </summary>
|
||||
public class WebPayParams
|
||||
{
|
||||
/// <summary>
|
||||
/// 支付数据
|
||||
/// </summary>
|
||||
public WebPayData? Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求支付URL
|
||||
/// </summary>
|
||||
public string RequestPay { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 提示信息
|
||||
/// </summary>
|
||||
public string Tips { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Web支付数据
|
||||
/// </summary>
|
||||
public class WebPayData
|
||||
{
|
||||
/// <summary>
|
||||
/// 订单号
|
||||
/// </summary>
|
||||
public string OrderNum { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,18 @@ public class WarehouseService : IWarehouseService
|
|||
private readonly HoneyBoxDbContext _dbContext;
|
||||
private readonly ILogger<WarehouseService> _logger;
|
||||
private readonly ILogisticsService _logisticsService;
|
||||
private readonly IWechatService _wechatService;
|
||||
|
||||
public WarehouseService(
|
||||
HoneyBoxDbContext dbContext,
|
||||
ILogger<WarehouseService> 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
|
||||
|
|
|
|||
|
|
@ -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<WechatService> logger,
|
||||
WechatSettings wechatSettings,
|
||||
IOptions<WechatPaySettings> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -394,4 +399,104 @@ public class WechatService : IWechatService
|
|||
_logger.LogWarning("未找到AppId {AppId} 的配置,使用默认AppSecret", appId);
|
||||
return _wechatSettings.AppSecret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建支付订单(Web支付方式)
|
||||
/// </summary>
|
||||
public async Task<CreatePayResult> 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<string, string>
|
||||
{
|
||||
{ "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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成订单号
|
||||
/// 格式:前缀(3位) + 商户前缀(3位) + 项目前缀(2位) + 支付类型(3位) + 时间戳 + 随机数
|
||||
/// </summary>
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ public class ServiceModule : Module
|
|||
var wechatSettings = c.Resolve<WechatSettings>();
|
||||
var wechatPaySettings = c.Resolve<Microsoft.Extensions.Options.IOptions<WechatPaySettings>>();
|
||||
var redisService = c.Resolve<IRedisService>();
|
||||
return new WechatService(httpClientFactory.CreateClient(), logger, wechatSettings, wechatPaySettings, redisService);
|
||||
var dbContext = c.Resolve<HoneyBoxDbContext>();
|
||||
return new WechatService(httpClientFactory.CreateClient(), logger, wechatSettings, wechatPaySettings, redisService, dbContext);
|
||||
}).As<IWechatService>().InstancePerLifetimeScope();
|
||||
|
||||
// 注册 IP 地理位置服务
|
||||
|
|
@ -218,7 +219,8 @@ public class ServiceModule : Module
|
|||
var dbContext = c.Resolve<HoneyBoxDbContext>();
|
||||
var logger = c.Resolve<ILogger<WarehouseService>>();
|
||||
var logisticsService = c.Resolve<ILogisticsService>();
|
||||
return new WarehouseService(dbContext, logger, logisticsService);
|
||||
var wechatService = c.Resolve<IWechatService>();
|
||||
return new WarehouseService(dbContext, logger, logisticsService, wechatService);
|
||||
}).As<IWarehouseService>().InstancePerLifetimeScope();
|
||||
|
||||
// ========== 支付系统服务注册 ==========
|
||||
|
|
|
|||
|
|
@ -473,10 +473,46 @@ public class SendResultDto
|
|||
public string OrderNo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付参数(需要支付运费时返回)
|
||||
/// 支付参数(需要支付运费时返回,支持Web支付和微信原生支付)
|
||||
/// </summary>
|
||||
[JsonPropertyName("res")]
|
||||
public WechatPayParamsDto? Res { get; set; }
|
||||
public WebPayResultDto? Res { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Web支付结果DTO(兼容前端支付流程)
|
||||
/// </summary>
|
||||
public class WebPayResultDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 支付数据(包含订单号)
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public WebPayDataDto? Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付请求URL
|
||||
/// </summary>
|
||||
[JsonPropertyName("requestPay")]
|
||||
public string RequestPay { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 提示信息
|
||||
/// </summary>
|
||||
[JsonPropertyName("tips")]
|
||||
public string Tips { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Web支付数据DTO
|
||||
/// </summary>
|
||||
public class WebPayDataDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 订单号
|
||||
/// </summary>
|
||||
[JsonPropertyName("order_num")]
|
||||
public string OrderNum { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user