diff --git a/Admin.NET/Admin.NET.Application/Configuration/Wechat.json b/Admin.NET/Admin.NET.Application/Configuration/Wechat.json index f591e84d..dc59db4e 100644 --- a/Admin.NET/Admin.NET.Application/Configuration/Wechat.json +++ b/Admin.NET/Admin.NET.Application/Configuration/Wechat.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", "Wechat": { @@ -16,16 +16,16 @@ }, // 微信支付 "WechatPay": { - "AppId": "", // 微信公众平台AppId、开放平台AppId、小程序AppId、企业微信CorpId - "MerchantId": "", // 商户平台的商户号 - "MerchantV3Secret": "", // 商户平台的APIv3密钥 - "MerchantCertificateSerialNumber": "", // 商户平台的证书序列号 + "AppId": "wxaaaaaaaaaaaaaa85", // 微信公众平台AppId、开放平台AppId、小程序AppId、企业微信CorpId + "MerchantId": "1500000001", // 商户平台的商户号 + "MerchantV3Secret": "3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaoo", // 商户平台的APIv3密钥 + "MerchantCertificateSerialNumber": "66aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0", // 商户平台的证书序列号 "MerchantCertificatePrivateKey": "\\WxPayCert\\apiclient_key.pem" // 商户平台的API证书私钥(apiclient_key.pem文件内容) }, // 支付回调 "PayCallBack": { - "WechatPayUrl": "https://xxx/api/sysWechatPay/payCallBack", // 微信支付回调 - "WechatRefundUrl": "", // 微信退款回调 + "WechatPayUrl": "https://ip/sysWechatPay/payCallBack", // 微信支付回调: https://ip/sysWechatPay/payCallBack + "WechatRefundUrl": "https://ip/api/sysWechatPay/refundCallBack", // 微信退款回调:https://ip/api/sysWechatPay/refundCallBack "AlipayUrl": "", // 支付宝支付回调 "AlipayRefundUrl": "" // 支付宝退款回调 } diff --git a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj index c4570d6a..588bad29 100644 --- a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj +++ b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj @@ -20,9 +20,9 @@ - - - + + + @@ -48,7 +48,7 @@ - + diff --git a/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs b/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs index 575cebfe..2e21069f 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs @@ -136,6 +136,12 @@ public partial class SysCodeGenConfig : EntityBase [SugarColumn(ColumnDescription = "是否是统计字段", Length = 8)] [MaxLength(8)] public string? Statistical { get; set; } + /// + /// 是否是GroupBy字段 + /// + [SugarColumn(ColumnDescription = "是否是GroupBy字段", Length = 8)] + [MaxLength(8)] + public string? IsGroupBy { get; set; } /// /// 是否是查询条件 diff --git a/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs b/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs index 19b96531..e81f07cc 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs @@ -18,104 +18,115 @@ public class SysWechatRefund : EntityBase /// 微信支付订单号(原支付交易对应的微信订单号) /// [SugarColumn(ColumnDescription = "微信支付订单号", Length = 32)] - [Required] + [Required] public string TransactionId { get; set; } /// /// 商户订单号(原交易对应的商户付款单号) /// [SugarColumn(ColumnDescription = "商户付款单号", Length = 32)] - [Required] + [Required] public string OutTradeNumber { get; set; } /// /// 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。 /// [SugarColumn(ColumnDescription = "商户退款单号", Length = 64)] - [Required] - public string OutRefundNo { get; set; } + [Required] + public string OutRefundNumber { get; set; } + + /// + /// 微信接口退款ID + /// + public string RefundId { get; set; } /// /// 退款原因,示例:商品已售完 /// - [SugarColumn(ColumnDescription = "退款原因", Length = 80)] + [SugarColumn(ColumnDescription = "退款原因", Length = 80)] public string Reason { get; set; } /// /// 退款金额 /// - [SugarColumn(ColumnDescription = "退款金额")] + [SugarColumn(ColumnDescription = "退款金额")] public int Refund { get; set; } /// /// 原订单总金额 /// - [SugarColumn(ColumnDescription = "订单总金额")] + [SugarColumn(ColumnDescription = "订单总金额")] public int Total { get; set; } /// /// 退款结果回调url /// - [SugarColumn(ColumnDescription = "退款结果回调url", Length = 256)] + [SugarColumn(ColumnDescription = "退款结果回调url", Length = 256)] public string? NotifyUrl { get; set; } /// /// 退款资金来源, 可不传,默认使用未结算资金退款(仅对老资金流商户适用) /// - [SugarColumn(ColumnDescription = "退款资金来源", Length = 32)] + [SugarColumn(ColumnDescription = "退款资金来源", Length = 32)] public string? FundsAccount { get; set; } /// /// 关联的商户订单号 /// - [SugarColumn(ColumnDescription = "关联的用户订单号", Length = 256)] + [SugarColumn(ColumnDescription = "关联的用户订单号", Length = 256)] public string? OrderId { get; set; } /// /// 关联的商户订单状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) /// - [SugarColumn(ColumnDescription = "关联的商户订单状态", Length = 32)] - public string? OrderStatus { get; set; } + [SugarColumn(ColumnDescription = "关联的商户订单状态", Length = 32)] + public string? RefundStatus { get; set; } + + /// + /// 支完成时间 + /// + [SugarColumn(ColumnDescription = "完成时间")] + public DateTime? SuccessTime { get; set; } /// /// 关联的商户商品编码 /// - [SugarColumn(ColumnDescription = "关联的商户商品编码", Length = 32)] - public string MerchantGoodsId { get; set; } + [SugarColumn(ColumnDescription = "关联的商户商品编码", Length = 32)] + public string? MerchantGoodsId { get; set; } /// /// 关联的商户商品名称 /// - [SugarColumn(ColumnDescription = "关联的商户商品名称", Length = 256)] - public string GoodsName { get; set; } + [SugarColumn(ColumnDescription = "关联的商户商品名称", Length = 256)] + public string? GoodsName { get; set; } /// /// 关联的商户商品单价 /// - [SugarColumn(ColumnDescription = "关联的商户商品单价")] + [SugarColumn(ColumnDescription = "关联的商户商品单价")] public int UnitPrice { get; set; } /// /// 关联的商户商品退款金额 /// - [SugarColumn(ColumnDescription = "关联的商户商品退款金额")] + [SugarColumn(ColumnDescription = "关联的商户商品退款金额")] public int RefundAmount { get; set; } /// /// 关联的商户商品退货数量 /// - [SugarColumn(ColumnDescription = "关联的商户商品退货数量")] + [SugarColumn(ColumnDescription = "关联的商户商品退货数量")] public int RefundQuantity { get; set; } = 1; /// /// 附加数据 /// - [SugarColumn(ColumnDescription = "附加数据")] + [SugarColumn(ColumnDescription = "附加数据")] public string? Attachment { get; set; } /// /// 备注 /// - [SugarColumn(ColumnDescription = "备注", ColumnDataType = StaticConfig.CodeFirst_BigString)] + [SugarColumn(ColumnDescription = "备注", ColumnDataType = StaticConfig.CodeFirst_BigString)] public string? Remark { get; set; } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs index fa13ecd0..c3bb3b94 100644 --- a/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs +++ b/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs @@ -222,6 +222,23 @@ public class SysCodeGenTemplateSeedData : ISqlSugarEntitySeedData public string Statistical { get; set; } + /// + /// 是否是GroupBy字段 + /// + public string? IsGroupBy { get; set; } /// /// 是否是查询条件 diff --git a/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs b/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs index c6441b0f..b997bef9 100644 --- a/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs +++ b/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs @@ -116,7 +116,7 @@ public class SysOnlineUserService : IDynamicApiController, ITransient [DisplayName("清理在线用户")] public async Task ClearOnline() { - if (await _sysConfigService.GetConfigValueByCode(ConfigConst.SysSingleLogin)) return; + if (!await _sysConfigService.GetConfigValueByCode(ConfigConst.SysSingleLogin)) return; // 相同账号最后登录的用户Id集合 var onlineUsers = await _sysOnlineUerRep.AsQueryable().GroupBy(u => u.UserId) diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs index b71954d7..84907694 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs @@ -140,13 +140,13 @@ public class PageSysWechatPayInput : BasePageInput /// order_id /// /// - public string? OrderId { get; set; } = "-1"; + public string? OrderId { get; set; } /// /// order_status /// /// - public string? OrderStatus { get; set; } = "-1"; + public string? OrderStatus { get; set; } /// /// out_trade_number diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs index 4fa2f986..095abdf7 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs @@ -15,6 +15,20 @@ namespace Admin.NET.Core.Service; [ApiDescriptionSettings(Order = 210, Description = "微信支付")] public class SysWechatPayService : IDynamicApiController, ITransient { + private static readonly List wechatPayEventHandlers = [new WechatPayEventInterceptor() { Order = int.MaxValue }]; + + /// + /// 注册支付记录变化事件处理器 + /// + /// 处理器 + /// 排序,数据越大越先执行 + public static void AddPayEventInterceptor(WechatPayEventInterceptor eh, int order) + { + eh.Order = order; + wechatPayEventHandlers.Add(eh); + wechatPayEventHandlers.Sort((a, b) => b.Order - a.Order); + } + private readonly SqlSugarRepository _sysWechatPayRep; private readonly SqlSugarRepository _sysWechatRefundRep; private readonly WechatPayOptions _wechatPayOptions; @@ -28,7 +42,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient IOptions payCallBackOptions) { _sysWechatPayRep = sysWechatPayRep; - this._sysWechatRefundRep = sysWechatRefundRep; + _sysWechatRefundRep = sysWechatRefundRep; _wechatPayOptions = wechatPayOptions.Value; _payCallBackOptions = payCallBackOptions.Value; @@ -187,7 +201,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient { string outTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000); // 微信需要的订单号(唯一) - //检查订单信息是否已存在(使用“商户交易单号+状态”唯一性判断) + // 检查订单信息是否已存在(使用“商户交易单号+状态”唯一性判断) var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OrderId == input.OrderId && u.OrderStatus == input.OrderStatus); if (wechatPay != null) { @@ -250,6 +264,82 @@ public class SysWechatPayService : IDynamicApiController, ITransient return await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == tradeId); } + /// + /// 获取支付订单详情(微信接口) 🔖 + /// + /// + /// + [DisplayName("获取支付订单详情(微信接口)")] + public async Task GetPayInfoFromWechat(string tradeId) + { + var request = new GetPayTransactionByOutTradeNumberRequest(); + request.OutTradeNumber = tradeId; + var response = await _wechatTenpayClient.ExecuteGetPayTransactionByOutTradeNumberAsync(request); + // 修改订单支付状态 + var wechatPayOld = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == response.OutTradeNumber + && u.MerchantId == response.MerchantId); + SysWechatPay wechatPayNew = null; + if (wechatPayOld != null) + wechatPayNew = wechatPayOld.DeepCopy(); + // 如果状态不一致就更新数据库中的记录 + if (wechatPayNew != null && wechatPayNew.TradeState != response.TradeState) + { + wechatPayNew.OpenId = response.Payer?.OpenId; + wechatPayNew.TransactionId = response.TransactionId; // 支付订单号 + wechatPayNew.TradeType = response.TradeType; // 交易类型 + wechatPayNew.TradeState = response.TradeState; // 交易状态 + wechatPayNew.TradeStateDescription = response.TradeStateDescription; // 交易状态描述 + wechatPayNew.BankType = response.BankType; // 付款银行类型 + if (response.Amount != null) + { + wechatPayNew.Total = response.Amount.Total; // 订单总金额 + wechatPayNew.PayerTotal = response.Amount.PayerTotal; // 用户支付金额 + } + if (response.SuccessTime.HasValue) + wechatPayNew.SuccessTime = response.SuccessTime.Value.DateTime; // 支付完成时间 + else + wechatPayNew.SuccessTime = DateTime.Now; + await _sysWechatPayRep.AsUpdateable(wechatPayNew).IgnoreColumns(true).ExecuteCommandAsync(); + } + if (wechatPayOld == null || wechatPayOld.TradeState != wechatPayNew.TradeState) + { + // 没必要等所有回调事件处理完再返回给微信,开一个Task执行 + Task.Run(async () => + { + foreach (var eh in wechatPayEventHandlers) + { + try + { + if (!await eh.PayInforChanged(wechatPayOld, wechatPayNew)) + break; + } + catch (Exception ex) + { + $"GetPayInfoFromWechat 中执行微信支付回调{eh.GetType().Name}出错".LogError(ex); + } + } + }).Start(); + } + // 下面这里创建一个新的对象,是因为不想把全部字段都返回 + wechatPayNew = new SysWechatPay() + { + AppId = _wechatPayOptions.AppId, + MerchantId = _wechatPayOptions.MerchantId, + SubAppId = _wechatPayOptions.AppId, + SubMerchantId = _wechatPayOptions.MerchantId, + OutTradeNumber = request.OutTradeNumber, + Attachment = response.Attachment, + TransactionId = response.TransactionId, + TradeType = response.TradeType, // 交易类型 + TradeState = response.TradeState, // 交易状态 + TradeStateDescription = response.TradeStateDescription, // 交易状态描述 + BankType = response.BankType, // 付款银行类型 + SuccessTime = response.SuccessTime.HasValue ? response.SuccessTime.Value.DateTime : DateTime.Now // 支付完成时间 + }; + + return wechatPayNew; + } + /// /// 微信支付成功回调(商户直连) 🔖 /// @@ -269,35 +359,97 @@ public class SysWechatPayService : IDynamicApiController, ITransient var callbackResource = _wechatTenpayClient.DecryptEventResource(callbackModel); // 修改订单支付状态 - var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber + var wechatPayOld = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber && u.MerchantId == callbackResource.MerchantId); - if (wechatPay == null) return null; - //wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识 - //wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号 - //wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号 - wechatPay.TransactionId = callbackResource.TransactionId; // 支付订单号 - wechatPay.TradeType = callbackResource.TradeType; // 交易类型 - wechatPay.TradeState = callbackResource.TradeState; // 交易状态 - wechatPay.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述 - wechatPay.BankType = callbackResource.BankType; // 付款银行类型 - wechatPay.Total = callbackResource.Amount.Total; // 订单总金额 - wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额 - wechatPay.SuccessTime = callbackResource.SuccessTime; // 支付完成时间 - - await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + if (wechatPayOld == null) return null; + var wechatPayNew = wechatPayOld.DeepCopy(); + if (callbackResource.Payer != null) + wechatPayNew.OpenId = callbackResource.Payer.OpenId; // 支付者标识 + wechatPayNew.TransactionId = callbackResource.TransactionId; // 支付订单号 + wechatPayNew.TradeType = callbackResource.TradeType; // 交易类型 + wechatPayNew.TradeState = callbackResource.TradeState; // 交易状态 + wechatPayNew.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述 + wechatPayNew.BankType = callbackResource.BankType; // 付款银行类型 + wechatPayNew.Total = callbackResource.Amount.Total; // 订单总金额 + wechatPayNew.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额 + wechatPayNew.SuccessTime = callbackResource.SuccessTime; // 支付完成时间 + await _sysWechatPayRep.AsUpdateable(wechatPayNew).IgnoreColumns(true).ExecuteCommandAsync(); + // 因为这个是回调给微信的,所以这里没必要等所有回调事件处理完再返回给微信,开一个Task执行 + if (wechatPayOld.TradeState != wechatPayNew.TradeState) + { + Task.Run(async () => + { + foreach (var eh in wechatPayEventHandlers) + { + try + { + if (!await eh.PayInforChanged(wechatPayOld, wechatPayNew)) + break; + } + catch (Exception ex) + { + $"PayCallBack 中执行微信支付回调{eh.GetType().Name}出错".LogError(ex); + } + } + }).Start(); + } return new WechatPayOutput() { - Total = wechatPay.Total, - Attachment = wechatPay.Attachment, - GoodsTag = wechatPay.GoodsTag, - OrderId = long.Parse(wechatPay.OrderId) + Total = wechatPayNew.Total, + Attachment = wechatPayNew.Attachment, + GoodsTag = wechatPayNew.GoodsTag, + OrderId = long.Parse(wechatPayNew.OrderId) }; } return null; } + /// + /// 微信退款回调(商户直连) 🔖 + /// + /// + [AllowAnonymous] + [DisplayName("微信退款回调(商户直连)")] + public async Task RefundCallBack() + { + using var ms = new MemoryStream(); + await App.HttpContext.Request.Body.CopyToAsync(ms); + var b = ms.ToArray(); + var callbackJson = Encoding.UTF8.GetString(b); + + var callbackModel = _wechatTenpayClient.DeserializeEvent(callbackJson); + if ("REFUND.SUCCESS".Equals(callbackModel.EventType)) + { + // 参考:https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/refund-result-notice.html + try + { + var callbackRefundResource = _wechatTenpayClient.DecryptEventResource(callbackModel); + // 修改订单支付状态 + var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutRefundNumber == callbackRefundResource.OutRefundNumber); + if (wechatRefund == null) return null; + wechatRefund.RefundStatus = callbackRefundResource.RefundStatus; // 交易状态 + wechatRefund.SuccessTime = callbackRefundResource.SuccessTime.Value.DateTime; // 支付完成时间 + + await _sysWechatRefundRep.AsUpdateable(wechatRefund).IgnoreColumns(true).ExecuteCommandAsync(); + // 有退款,刷新一下订单状态(通过主动查询Wechat接口获取) + // 通过 GetPayInfoFromWechat 也会触发 WechatPayEventHandler,所以这个回调中不用主动调用 + await GetPayInfoFromWechat(callbackRefundResource.OutTradeNumber); + } + catch (Exception ex) + { + "微信退款回调时出错:".LogError(ex); + } + } + else + { + callbackModel.EventType.LogInformation(); + } + + return null; + } + /// /// 微信支付成功回调(服务商模式) 🔖 /// @@ -317,22 +469,43 @@ public class SysWechatPayService : IDynamicApiController, ITransient var callbackResource = _wechatTenpayClient.DecryptEventResource(callbackModel); // 修改订单支付状态 - var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber + var wechatPayOld = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber && u.MerchantId == callbackResource.MerchantId); - if (wechatPay == null) return; + + if (wechatPayOld == null) return; + var wechatPayNew = wechatPayOld.DeepCopy(); //wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识 //wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号 //wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号 - wechatPay.TransactionId = callbackResource.TransactionId; // 支付订单号 - wechatPay.TradeType = callbackResource.TradeType; // 交易类型 - wechatPay.TradeState = callbackResource.TradeState; // 交易状态 - wechatPay.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述 - wechatPay.BankType = callbackResource.BankType; // 付款银行类型 - wechatPay.Total = callbackResource.Amount.Total; // 订单总金额 - wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额 - wechatPay.SuccessTime = callbackResource.SuccessTime; // 支付完成时间 + wechatPayNew.TransactionId = callbackResource.TransactionId; // 支付订单号 + wechatPayNew.TradeType = callbackResource.TradeType; // 交易类型 + wechatPayNew.TradeState = callbackResource.TradeState; // 交易状态 + wechatPayNew.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述 + wechatPayNew.BankType = callbackResource.BankType; // 付款银行类型 + wechatPayNew.Total = callbackResource.Amount.Total; // 订单总金额 + wechatPayNew.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额 + wechatPayNew.SuccessTime = callbackResource.SuccessTime; // 支付完成时间 - await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + await _sysWechatPayRep.AsUpdateable(wechatPayNew).IgnoreColumns(true).ExecuteCommandAsync(); + // 因为这个是回调给微信的,所以这里没必要等所有回调事件处理完再返回给微信,开一个Task执行 + if (wechatPayOld.TradeState != wechatPayNew.TradeState) + { + Task.Run(async () => + { + foreach (var eh in wechatPayEventHandlers) + { + try + { + if (!await eh.PayInforChanged(wechatPayOld, wechatPayNew)) + break; + } + catch (Exception ex) + { + $"PayPartnerCallBack 中执行微信支付回调{eh.GetType().Name}出错".LogError(ex); + } + } + }).Start(); + } } } @@ -345,10 +518,16 @@ public class SysWechatPayService : IDynamicApiController, ITransient [DisplayName("微信退款申请)")] public async Task Refund(RefundRequestInput input) { + var vechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == input.OutTradeNumber); + if (vechatPay == null) + { + throw Oops.Bah("没有相应支付记录"); + } var request = new CreateRefundDomesticRefundRequest() { OutTradeNumber = input.OutTradeNumber, OutRefundNumber = "REFUND_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"), + NotifyUrl = _payCallBackOptions.WechatRefundUrl, Amount = new CreateRefundDomesticRefundRequest.Types.Amount() { Total = input.Total, @@ -359,7 +538,15 @@ public class SysWechatPayService : IDynamicApiController, ITransient var response = await _wechatTenpayClient.ExecuteCreateRefundDomesticRefundAsync(request); if (!response.IsSuccessful()) + { + // 退款失败,该单可能已经退款了,所以主动查询微信接口更新状态 + try + { + await this.GetPayInfoFromWechat(input.OutTradeNumber); + } + catch (Exception ex) { } throw Oops.Oh($"JSAPI 退款申请失败(状态码:{response.GetRawStatus()},错误代码:{response.ErrorCode},错误描述:{response.ErrorMessage})"); + } var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutTradeNumber == input.OutTradeNumber); if (wechatRefund == null) @@ -367,15 +554,16 @@ public class SysWechatPayService : IDynamicApiController, ITransient // 保存退款申请信息 wechatRefund = new SysWechatRefund() { - TransactionId = input.OutTradeNumber, - OutTradeNumber = request.OutTradeNumber, - OutRefundNo = request.OutTradeNumber, // 每笔付款只退一次,所以这里直接用付款单号 + TransactionId = vechatPay.TransactionId, + OutTradeNumber = input.OutTradeNumber, + OutRefundNumber = request.OutRefundNumber, // 每笔付款只退一次,所以这里直接用付款单号 Reason = request.Reason, Refund = input.Refund, + RefundId = response.RefundId, Total = input.Total, - //NotifyUrl = "", + NotifyUrl = _payCallBackOptions.WechatRefundUrl, OrderId = input.OrderId, - OrderStatus = input.OrderStatus, + RefundStatus = input.OrderStatus, MerchantGoodsId = input.MerchantGoodsId, GoodsName = input.GoodsName, UnitPrice = input.UnitPrice, @@ -385,6 +573,8 @@ public class SysWechatPayService : IDynamicApiController, ITransient Remark = input.Remark }; await _sysWechatRefundRep.InsertAsync(wechatRefund); + // 发送了退款请求也要更新原定单的状态(从微信查询) + await this.GetPayInfoFromWechat(input.OutTradeNumber); } } @@ -530,10 +720,14 @@ public class SysWechatPayService : IDynamicApiController, ITransient /// 根据支付Id获取退款信息列表 🔖 /// /// + /// < /// [DisplayName("根据支付Id获取退款信息列表")] - public async Task> GetRefundList([FromQuery] string transactionId) + public async Task> GetRefundList([FromQuery] string transactionId, [FromQuery] string outTradeNumber) { - return await _sysWechatRefundRep.AsQueryable().Where(u => u.TransactionId == transactionId).ToListAsync(); + return await _sysWechatRefundRep.AsQueryable() + .WhereIF(!string.IsNullOrEmpty(transactionId), u => u.TransactionId == transactionId) + .WhereIF(!string.IsNullOrEmpty(outTradeNumber), u => u.OutTradeNumber == outTradeNumber) + .ToListAsync(); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/WechatPayEventInterceptor.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/WechatPayEventInterceptor.cs new file mode 100644 index 00000000..1647ceec --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/WechatPayEventInterceptor.cs @@ -0,0 +1,30 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 微信支传订单状态变化事件拦截器 +/// +public class WechatPayEventInterceptor +{ + /// + /// 数值越大越先执行 + /// + public int Order = 0; + + /// + /// 信息变化处理器 + /// + /// 主要用来判断 TradeStatus 的状态,这个状态有"空",NOTPAY,SUCCESS, REFUND + /// 旧记录状态 + /// 新记录状态 + /// + public virtual async Task PayInforChanged(SysWechatPay oldInfo, SysWechatPay newInfo) + { + return await Task.FromResult(true); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs b/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs new file mode 100644 index 00000000..235346ac --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Admin.NET.Core.Utils; +// 聚合配置增强版(独立类) +public class AggregationBuilder +{ + private readonly List _configs; + private readonly Type _entityType; + private readonly Type _outputType; + + public List SelectParts { get; } = new(); + public List HavingConditions { get; } = new(); + + public AggregationBuilder( + IEnumerable configs, + Type entityType, + Type outputType) + { + _configs = configs.ToList(); + _entityType = entityType; + _outputType = outputType; + Build(); + } + + private void Build() + { + foreach (var config in _configs.Where(IsValidConfig)) + { + // 处理SELECT部分 + var expression = string.IsNullOrEmpty(config.CustomExpression) + ? $"{config.Function.ToString().ToUpper()}({config.Field})" + : config.CustomExpression; + + SelectParts.Add($"{expression} AS {config.Alias}"); + + // 处理HAVING条件 + if (!string.IsNullOrEmpty(config.HavingCondition)) + { + HavingConditions.Add($"{expression} {config.HavingCondition}"); + } + } + } + + private bool IsValidConfig(AggregationConfig config) + { + // 字段基础验证 + if (!string.IsNullOrEmpty(config.Field) && + _entityType.GetProperty(config.Field) == null) + return false; + + // 输出属性验证 + return _outputType.GetProperty(config.Alias) != null; + } + /// + /// 验证聚合配置有效性 + /// + private bool ValidateAggregation(AggregationConfig config, Type entityType, Type outputType) + { + // 验证实体字段存在性 + var entityProp = entityType.GetProperty(config.Field); + if (entityProp == null) return false; + + // 验证输出字段存在性 + var outputProp = outputType.GetProperty(config.Alias); + if (outputProp == null) return false; + + // 验证类型兼容性 + return config.Function switch + { + AggregateFunction.Count => outputProp.PropertyType == typeof(int), + _ => outputProp.PropertyType == entityProp.PropertyType || + IsNumericType(outputProp.PropertyType) + }; + } + + private static bool IsNumericType(Type type) + { + return Type.GetTypeCode(type) switch + { + TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 + or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 + or TypeCode.Int32 or TypeCode.Int64 or TypeCode.Decimal + or TypeCode.Double or TypeCode.Single => true, + _ => false + }; + } + /// + /// 验证字段有效性 + /// + public static List ValidateFields(string[] fields, Type targetType) + { + if (fields == null || fields.Length == 0) return new List(); + + return fields + .Where(f => !string.IsNullOrWhiteSpace(f)) + .Select(f => f.Trim()) + .Where(f => targetType.GetProperty( + targetType == typeof(UsagetokenOutput) ? + $"{f}Sum" : f) != null) + .ToList(); + } +} + +// 增强版聚合配置类 +public class AggregationConfig +{ + /// + /// 数据库字段名(与CustomExpression二选一) + /// + public string Field { get; set; } + + /// + /// 自定义聚合表达式(优先级高于Field+Function) + /// + public string CustomExpression { get; set; } + + /// + /// 聚合函数类型(使用CustomExpression时可不填) + /// + public AggregateFunction Function { get; set; } = AggregateFunction.Sum; + + /// + /// 输出字段别名(必须与DTO属性名一致) + /// + public required string Alias { get; set; } + + /// + /// HAVING条件表达式(如"> 100") + /// + public string HavingCondition { get; set; } +} +public enum AggregateFunction +{ + Sum, + Avg, + Count, + Max, + Min, + // 可扩展其他函数 +} diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm index e50562a7..52565d29 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm @@ -17,7 +17,7 @@ namespace @Model.NameSpace; @if(@Model.ConfigId!="1300000000001"){ @:[Tenant("@(@Model.ConfigId)")] } -public partial class @(@Model.EntityName) @Model.BaseClassName +public class @(@Model.EntityName) @Model.BaseClassName { @foreach (var column in Model.TableField){ if(@Model.BaseClassName=="" && @column.IsPrimarykey){ diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm index 008afc22..c2f9bf9c 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm @@ -1,28 +1,60 @@ - -namespace @(@Model.NameSpace).Entity; +@if(@Model.NameSpace != "Admin.NET.Core"){ +@:using Admin.NET.Core; +} +namespace @(@Model.NameSpace); /// -/// 扩展@(@Model.BusName)实体 +/// 扩展@(@Model.BusName)输出实体 /// -public partial class @(@Model.ClassName) +public partial class @(@Model.ClassName)Output { - - @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){ - @:/// - @:/// 合约商品树形Name - @:/// - @:[SugarColumn(IsIgnore = true)] - @:public string Name { get; set; } - } - /// - /// @(@Model.BusName)子项 - /// - [SugarColumn(IsIgnore = true)] - public List<@(@Model.ClassName)> Children { get; set; } + /// - /// 是否禁止选中 + /// count /// - [SugarColumn(IsIgnore = true)] - public bool Disabled { get; set; } + public int count { get; set; } + @foreach (var par in Model.TableInoutpar.Where(m => m.inouttype == "Input").ToList()){ + @:/// + @:/// @(@par.Name) + @:/// + @:public @(@par.DataType) @(@par.parameter) { get; set; } + } + + @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){ + @:/// + @:/// Name + @:/// + @:public string Name { get; set; } + } +@if(Model.TabType=="Tree"){ + @:/// + @:/// @(@Model.BusName)子项 + @:/// + @:[SugarColumn(IsIgnore = true)] + @:public List<@(@Model.ClassName)Output> Children { get; set; } + + @:/// + @:/// 是否禁止选中 + @:/// + @:[SugarColumn(IsIgnore = true)] + @:public bool Disabled { get; set; } +} } + +/// +/// 扩展@(@Model.BusName)分页查询输入参数 +/// +public partial class Page@(@Model.ClassName)Input +{ + public string[] GroupBy { get; set; } + public string[] Sum { get; set; } + public IEnumerable Aggregations { get; set; } + @foreach (var par in Model.TableInoutpar.Where(m => m.inouttype == "Output").ToList()){ + @:/// + @:/// @(@par.Name) + @:/// + @:public @(@par.DataType) @(@par.parameter) { get; set; } +} + +} diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm index ab89091e..04e35e14 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm @@ -1,4 +1,3 @@ -using Admin.NET.Core.Service; using Microsoft.AspNetCore.Http; using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; @{ @@ -19,7 +18,9 @@ using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; } } } -using @(@Model.NameSpace).Entity; +@if(@Model.NameSpace != "Admin.NET.Core"){ +@:using Admin.NET.Core; +} namespace @Model.NameSpace; /// @@ -35,26 +36,35 @@ public partial class @(@Model.ClassName)Service /// [ApiDescriptionSettings(Name = "GetTreeList", Description = "获取列表", Order = 1100), HttpPost] [DisplayName("获取列表")] - public async Task> GetTreeList([FromQuery] Tree input) + public async Task> GetTreeList(Page@(@Model.ClassName)Input input) { - // 带条件筛选时返回列表数据 - var list = await _@(@Model.LowerClassName)Rep.AsQueryable() - .WhereIF(input.Id > 0, t => t.Id == input.Id) - //名称 - //.WhereIF(!string.IsNullOrWhiteSpace(input.tacticsNme), u => u.tacticsNme.Contains(input.tacticsNme.Trim())) - .OrderBy(t => new { t.Id }) - //.Select((u) => new Tree - //{ - // Id = u.Id.ToString(), - // Name = u.@(@Model.TreeName) - //}) - .ToListAsync(); - @if(!string.IsNullOrEmpty(Model.TreeName)){ - @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){ - @:list.ForEach(t => t.Name = t.@(@Model.TreeName) ); - } - } - return list; + @if(Model.TabType=="Tree"){ + @:var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToTreeAsync(u => u.Children, u => u.@(@Model.TreeKey), input.Id); + @://var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync();//非树形结构表 + + @:var md = await _@(@Model.LowerClassName)Rep.AsQueryable().Where(u => u.Id == input.Id).Select<@(@Model.ClassName)Output>().FirstAsync(); + @:if (md == null) return list; + @: + @:md.Children = list; + @:list = [md]; + @:return list; + }else{ + @://return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToTreeAsync(u => u.Children, u => u.@(@Model.TreeKey), input.Id));//树形结构表 + @:return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync(); + } } } +/// +/// 扩展@(@Model.BusName)中间件 +/// +public partial class @(@Model.ClassName)Mid +{ + +} +@{ +string LowerFirstLetter(string text) +{ +return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写 +} +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm index acd91601..a35291df 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm @@ -41,7 +41,7 @@ if (@column.ColumnKey != "True"){ /// /// @(@Model.BusName)分页查询输入参数 /// - public class Page@(@Model.ClassName)Input : BasePageInput + public partial class Page@(@Model.ClassName)Input : BasePageInput { /// /// 关键字查询 diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm new file mode 100644 index 00000000..621a81b9 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm @@ -0,0 +1,116 @@ +@{ + string joinTableName = "u"; + Dictionary definedObjects = new Dictionary(); + bool haveLikeCdt = false; + string RemoteField=""; + string PKName=""; + foreach (var column in Model.TableField){ + if (column.QueryWhether == "Y" && column.QueryType == "like"){ + haveLikeCdt = true; + } + if(column.RemoteVerify){ + RemoteField=@column.PropertyName; + } + if(column.ColumnKey == "True"){ + PKName=column.PropertyName; + } + } +} +@if(@Model.NameSpace != "Admin.NET.Core"){ +@:using Admin.NET.Core; +} + +namespace @(@Model.NameSpace); +public partial class @(@Model.ClassName)Mid +{ + /// + /// 获取查询 + /// + /// + /// + /// + public static ISugarQueryable<@(@Model.ClassName)Output> GetQuery(SqlSugarRepository<@(@Model.ClassName)> _@(@Model.LowerClassName)Rep,Page@(@Model.ClassName)Input input) + { + var sysCacheService = App.GetRequiredService(); + var db = App.GetRequiredService(); + @if (haveLikeCdt) { + @:input.SearchKey = input.SearchKey?.Trim(); + } + var query = _@(@Model.LowerClassName)Rep.AsQueryable() + @{string conditionFlag = "";} + @if (haveLikeCdt) { + @:.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => + @foreach (var column in Model.TableField){ + if (@column.QueryWhether == "Y" && column.QueryType == "like"){ + @:@(conditionFlag)u.@(@column.PropertyName).Contains(input.SearchKey) + conditionFlag="|| "; + } + } + @:) + } + @foreach (var column in Model.TableField){ + if (@column.QueryWhether == "Y"){ + if (@column.NetType?.TrimEnd('?') == "string"){ + if(@column.QueryType == "like"){ + @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName).Contains(input.@(@column.PropertyName).Trim())) + }else{ + @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + } + }else if(@column.NetType?.TrimEnd('?') == "int" || @column.NetType?.TrimEnd('?') == "long"){ + @:.WhereIF(input.@column.PropertyName>0, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + }else if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){ + @:.WhereIF(input.@(@column.PropertyName)Range != null && input.@(@column.PropertyName)Range.Length == 2, u => u.@(@column.PropertyName) >= input.@(@column.PropertyName)Range[0] && u.@(@column.PropertyName) <= input.@(@column.PropertyName)Range[1]) + }else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true) { + @:.WhereIF(input.@(@column.PropertyName).HasValue, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + } + } + } + + .Select<@(@Model.ClassName)Output>() + @if(!string.IsNullOrEmpty(Model.TreeName)){ + @:.Mapper(c => c.Name= c.@(@Model.TreeName).ToString()) + } + @foreach (var column in Model.TableField){ + if(@column.EffectType == "Upload"){ + @://.Mapper(c => c.@(@column.PropertyName)Attachment, c => c.@(@column.PropertyName)) + } + else if(@column.EffectType == "ForeignKey"){ + @:.Mapper(t => + @:{ + @: //使用缓存 + @: var key = $"@(@column.FkEntityName)_{t.@(@column.PropertyName)}"; + @: if (!sysCacheService.ExistKey(key)) + @: { + @: var m = db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName)); + @: if (m != null) sysCacheService.Set(key, m); + @: } + @: t.@(@column.PropertyName)@(@column.FkColumnName) = sysCacheService.Get<@(@column.FkEntityName)>(key)?.@(@column.FkColumnName); + @: //t.@(@column.PropertyName)@(@column.FkColumnName)=db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName))).@(@column.FkColumnName);// + @:}) + } + else if(@column.EffectType == "ApiTreeSelector"){ + @:.Mapper(t => + @:{ + @: //使用缓存 + @: var key = $"@(@column.FkEntityName)_{t.@(@column.PropertyName)}"; + @: if (!sysCacheService.ExistKey(key)) + @: { + @: var m = db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.ValueColumn) == t.@(@column.PropertyName)); + @: if (m != null) sysCacheService.Set(key, m); + @: } + @: t.@(@column.PropertyName)@(@column.DisplayColumn) = sysCacheService.Get<@(@column.FkEntityName)>(key)?.@(@column.DisplayColumn); + @: //t.@(@column.PropertyName)@(@column.FkColumnName)=db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName))).@(@column.FkColumnName);// + @:}) + } + } + ; + + return query; + } +} +@{ +string LowerFirstLetter(string text) +{ +return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写 +} +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm index ec2e5cf1..846b0515 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm @@ -9,7 +9,7 @@ namespace @Model.NameSpace; /// /// @(@Model.BusName)输出参数 /// -public class @(@Model.ClassName)Output +public partial class @(@Model.ClassName)Output { @foreach (var column in Model.TableField){ @:/// diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm index a8183649..22a284d8 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm @@ -71,65 +71,9 @@ public partial class @(@Model.ClassName)Service : IDynamicApiController, ITransi [DisplayName("分页查询@(@Model.BusName)")] public async Task> Page(Page@(@Model.ClassName)Input input) { -@if (haveLikeCdt) { - @:input.SearchKey = input.SearchKey?.Trim(); -} - var query = _@(@Model.LowerClassName)Rep.AsQueryable() -@{string conditionFlag = "";} -@if (haveLikeCdt) { - @:.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => - @foreach (var column in Model.TableField){ - if (@column.QueryWhether == "Y" && column.QueryType == "like"){ - @:@(conditionFlag)u.@(@column.PropertyName).Contains(input.SearchKey) - conditionFlag="|| "; - } - } - @:) -} -@foreach (var column in Model.TableField){ -if (@column.QueryWhether == "Y"){ - if (@column.NetType?.TrimEnd('?') == "string"){ - if(@column.QueryType == "like"){ - @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName).Contains(input.@(@column.PropertyName).Trim())) - }else{ - @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) - } - }else if(@column.NetType?.TrimEnd('?') == "int" || @column.NetType?.TrimEnd('?') == "long"){ - @:.WhereIF(input.@column.PropertyName>0, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) - }else if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){ - @:.WhereIF(input.@(@column.PropertyName)Range != null && input.@(@column.PropertyName)Range.Length == 2, u => u.@(@column.PropertyName) >= input.@(@column.PropertyName)Range[0] && u.@(@column.PropertyName) <= input.@(@column.PropertyName)Range[1]) - }else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true) { - @:.WhereIF(input.@(@column.PropertyName).HasValue, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) - } -} -} -@foreach (var column in Model.TableField){ - if(@column.EffectType == "Upload"){ - @://.Mapper(c => c.@(@column.PropertyName)Attachment, c => c.@(@column.PropertyName)) - } -} - .Select<@(@Model.ClassName)Output>(); - - - - @if(@Model.TableField.Any(x=>x.EffectType == "ForeignKey"||x.EffectType == "ApiTreeSelector")){ - @:var list = await query.MergeTable().OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); - } else { - @:var list = await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); - } - @if(@Model.TableField.Any(x=>x.EffectType == "ForeignKey"||x.EffectType == "ApiTreeSelector")){ - @:list.Items.ForEach(t =>{ - - @foreach (var column in Model.TableField){ - if(@column.EffectType == "ForeignKey"){ - @:t.@(@column.PropertyName)@(@column.FkColumnName) =_@(@column.LowerFkEntityName)Rep.GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName)).@(@column.FkColumnName); - } - else if(@column.EffectType == "ApiTreeSelector"){ - @:t.@(@column.PropertyName)@(@column.FkColumnName) =_@(@column.LowerFkEntityName)Rep.GetFirst(f => f.@(@column.DisplayColumn) == t.@(@column.PropertyName)).@(@column.FkColumnName); - } - } - @:}); - } + //var query= @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input); + //var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).MergeTable().ToPagedListAsync(input.Page, input.PageSize); + var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); return list; } @@ -217,14 +161,125 @@ if (@column.ColumnKey == "True"){ /// /// /// - [ApiDescriptionSettings(Name = "list", Description = "获取@(@Model.BusName)列表", Order = 950), HttpGet] + [ApiDescriptionSettings(Name = "list", Description = "获取@(@Model.BusName)列表", Order = 950), HttpPost] [DisplayName("获取@(@Model.BusName)列表")] - public async Task> List([FromQuery] Page@(@Model.ClassName)Input input) + public async Task> List(Page@(@Model.ClassName)Input input) { - return await _@(@Model.LowerClassName)Rep.AsQueryable().Select<@(@Model.ClassName)Output>().ToListAsync(); + return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync(); } +@if(@Model.TableField.Any(x=>x.Statistical == "Y")){ + @:/// + @:/// 获取@(@Model.BusName) + @:/// + @:/// + @:/// + @:[ApiDescriptionSettings(Name = "GetTotalSum", Description = "获取@(@Model.BusName)统计", Order = 960), HttpPost] + @:[DisplayName("获取@(@Model.BusName)统计")] + @:public async Task> GetTotalSum(Page@(@Model.ClassName)Input input) + @:{ + @:// 单次查询同时获取统计值 + @:var querystats = @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input) + @foreach (var column in Model.TableField){ + if (@column.IsGroupBy == "Y"){ + @: .GroupByIF(input.GroupBy.Contains("@column.PropertyName"), u => u.@column.PropertyName) + } + } + @: //.Having(it => SqlFunc.AggregateCount(it.Id) > 0)//聚合函数过滤 + @: .Select(it => new @(@Model.ClassName)Output + @: { + @: count = SqlFunc.AggregateCount(it.Id), + @foreach (var column in Model.TableField){ + if (@column.IsGroupBy == "Y"){ + @: @(@column.PropertyName) = it.@(@column.PropertyName), + } + if (@column.Statistical == "Y"){ + @: @(@column.PropertyName) = SqlFunc.AggregateSum(it.@(@column.PropertyName)) ?? 0, + } + } + @: }); + @:return await querystats.ToListAsync(); + @:} +} + + /// + /// 根据输入参数获取@(@Model.BusName)统计 + /// 支持双模式聚合配置: + /// 常规模式:Field + Function + /// 高级模式:CustomExpression(支持任意合法SQL表达式) + /// 智能条件组合: + /// 多个HAVING条件自动用AND连接 + /// 条件表达式自动包裹聚合函数(如SUM(cost) > 10000) + /// 可扩展支持OR条件 + /// + /// + /// + [ApiDescriptionSettings(Name = "GetAggregTotalSum", Order = 970), HttpPost] + [DisplayName("根据输入参数获取@(@Model.BusName)统计")] + public async Task> GetAggregTotalSum(Page@(@Model.ClassName)Input input) + { + + @:var query = @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input) + // 输入参数示例 + //input = new Page@(@Model.ClassName)Input + //{ + // GroupBy = ["department", "project"], + // GroupBy = input.GroupBy, + // Aggregations = + // [ + // new AggregationConfig + //{ + // Field = "cost", + // Function = AggregateFunction.Sum, + // Alias = "totalCost", + // HavingCondition = "> 10000" + //}, + //new AggregationConfig + //{ + // CustomExpression = "AVG(CAST(response_time AS FLOAT))", + // Alias = "avgResponse", + // HavingCondition = "< 500" + //} + // ] + //}; + // 生成SQL示例 + // SELECT + // department, project, + // SUM(cost) AS totalCost, + // AVG(CAST(response_time AS FLOAT)) AS avgResponse + // FROM... + // GROUP BY department, project + // HAVING(SUM(cost) > 10000) AND(AVG(CAST(response_time AS FLOAT)) < 500) + + // 处理分组字段 + var groupFields = AggregationBuilder.ValidateFields(input.GroupBy, typeof(@(@Model.ClassName)Output)); + if (groupFields.Count > 0) + { + query = query.GroupBy(string.Join(",", groupFields)); + } + // 构建聚合配置 + var aggregator = new AggregationBuilder( + configs: input.Aggregations, + entityType: typeof(@(@Model.ClassName)), + outputType: typeof(@(@Model.ClassName)Output) + ); + + // 组合SELECT语句 + var selectParts = groupFields.Select(f => $"{f} AS {f}") + .Concat(aggregator.SelectParts) + .ToList(); + + // 应用HAVING条件 + if (aggregator.HavingConditions.Count > 0) + { + query = query.Having(string.Join(" AND ", aggregator.HavingConditions)); + } + // 执行查询 + return await query.Select<@(@Model.ClassName)Output>(string.Join(", ", selectParts)) + .ToListAsync(); + } + @foreach (var column in Model.TableField){ if(@column.EffectType == "ForeignKey" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){ @:/// @@ -270,7 +325,7 @@ if(@column.EffectType == "ApiTreeSelector" && !definedObjects.ContainsKey("@(@co @:[DisplayName("获取@(@column.FkEntityName)Tree")] @:public async Task @(@column.FkEntityName)Tree() @:{ - @:return await _@(@Model.LowerClassName)Rep.Context.Queryable<@(@column.FkEntityName)>().ToTreeAsync(u => u.Children, u => u.@(@column.PidColumn), 0); + @:return await _@(@Model.LowerClassName)Rep.Context.Queryable<@(@column.FkEntityName)>().Select<@(@column.FkEntityName)Output>().ToTreeAsync(u => u.Children, u => u.@(@column.PidColumn), 0); @:} } } diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm index cf7594b8..eea5ff09 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm @@ -18,6 +18,8 @@ enum Api { Update@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/update', Page@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/page', Tree@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/GetTreeList', + Get@(@Model.ClassName)TotalSum = '/api/@(@Model.LowerClassName)/GetTotalSum', + Get@(@Model.ClassName)AggregTotalSum = '/api/@(@Model.LowerClassName)/GetAggregTotalSum', Detail@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/detail', @if(Model.RemoteVerify){ @:Exists@(RemoteField) = '/api/@(@Model.LowerClassName)/exists@(RemoteField)', @@ -72,6 +74,20 @@ export const treelist@(@Model.ClassName) = (params?: any) => method: 'post', data: params, }); +// 获取统计,支持全表和分组 +export const get@(@Model.ClassName)TotalSum = (params?: any) => + request({ + url: Api.Get@(@Model.ClassName)TotalSum, + method: 'post', + data: params, + }); +// 输入参数获取统计 +export const get@(@Model.ClassName)AggregTotalSum = (params?: any) => + request({ + url: Api.Get@(@Model.ClassName)AggregTotalSum, + method: 'post', + data: params, + }); // 详情@(@Model.BusName) export const detail@(@Model.ClassName) = (id: any) => request({ diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm index 171b618e..db8effb6 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm @@ -52,7 +52,7 @@ - + @@ -60,7 +60,7 @@ - + @@ -235,7 +235,7 @@ import ModifyRecord from '/@@/components/table/modifyRecord.vue'; @:import { @(@Model.ClassName), @(@Model.ClassName)Input, @(@Model.ClassName)Output } from '/@@/api-services/models'; } else { -@:import { page@(@Model.ClassName), delete@(@Model.ClassName) } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)'; +@:import { page@(@Model.ClassName), delete@(@Model.ClassName), get@(@Model.ClassName)TotalSum } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)'; foreach (var column in Model.QueryWhetherList){ if(@column.EffectType == "ForeignKey"){ @:import { get@(@column.FkEntityName)@(@column.PropertyName)Dropdown } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)'; @@ -278,6 +278,7 @@ const state = reactive({ pageSize: 50 as number, defaultSort: { field: 'Id', order: 'asc', descStr: 'desc' }, }, + totalSum:[] as any, visible: false, title: '', }); @@ -337,6 +338,26 @@ const checkTableColumnVisible = (tableColumnName: any) => { } { title: '操作', fixed: 'right', width: 180, showOverflow: true, slots: { default: 'row_buttons' } }, ], + @if(@Model.TableField.Any(x=>x.Statistical == "Y")){ + @:footerMethod: ({ columns, data }) => { + @: const totalSum=state.totalSum[0]; + @: return [ + @: columns.map((column, colIndex) => { + @: if (colIndex === 0) { + @: return `合计:` + @: } + @foreach (var column in Model.TableField){ + if (@column.Statistical == "Y"){ + @: if (column.field === '@(@column.LowerPropertyName)') { + @: // 计算表格内总和 + @: return `${data.reduce((sum, row) => sum + (row.@(@column.LowerPropertyName) || 0), 0)}/总数:${totalSum.@(@column.LowerPropertyName)}` + @: } + } + } + @: }) + @: ] + @:}, + } }, // vxeGrid配置参数(此处可覆写任何参数),参考vxe-table官方文档 { @@ -377,6 +398,14 @@ const handleQueryApi = async (page: VxeGridPropTypes.ProxyAjaxQueryPageParams, s // 查询操作 const handleQuery = async (reset = false) => { options.loading = true; + @if(@Model.TableField.Any(x=>x.Statistical == "Y")){ + @if (@Model.IsApiService) { + @:state.totalSum =getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)GetTotalSumPost(params).data.result; + } + } else { + @:state.totalSum =(await get@(@Model.ClassName)TotalSum(state.queryParams)).data.result; + } + } reset ? await xGrid.value?.commitProxy('reload') : await xGrid.value?.commitProxy('query'); options.loading = false; }; diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm index 52f88582..255cb9ec 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm @@ -109,7 +109,11 @@ watch(filterText, (val) => { // 获取树数据 const fetchTreeData = async (showLoading: boolean = true) => { if (showLoading) state.loading = true; - var res = await treelist@(@Model.ClassName)({Id:0}); + @if (@Model.IsApiService) { + @:var res = await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)GetTreeListPost({Id:0}); + } else { + @:var res = await treelist@(@Model.ClassName)({Id:0}); + } state.@(@Model.LowerClassName)Data = res.data.result ?? []; if (showLoading) state.loading = false; return res.data.result ?? []; diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Admin.NET.Plugin.ReZero.csproj b/Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Admin.NET.Plugin.ReZero.csproj index 8dd211d3..483e2d0f 100644 --- a/Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Admin.NET.Plugin.ReZero.csproj +++ b/Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Admin.NET.Plugin.ReZero.csproj @@ -25,7 +25,7 @@ - + diff --git a/Web/package.json b/Web/package.json index 1953e0ec..f3dfe892 100644 --- a/Web/package.json +++ b/Web/package.json @@ -2,7 +2,7 @@ "name": "admin.net.pro", "type": "module", "version": "2.4.33", - "lastBuildTime": "2025.02.23", + "lastBuildTime": "2025.02.25", "description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架", "author": "zuohuaijun", "license": "MIT", @@ -88,22 +88,22 @@ "@types/node": "^20.17.19", "@types/nprogress": "^0.2.3", "@types/sortablejs": "^1.15.8", - "@typescript-eslint/eslint-plugin": "^8.24.1", - "@typescript-eslint/parser": "^8.24.1", + "@typescript-eslint/eslint-plugin": "^8.25.0", + "@typescript-eslint/parser": "^8.25.0", "@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue-jsx": "^4.1.1", "@vue/compiler-sfc": "^3.5.13", - "code-inspector-plugin": "^0.20.0", + "code-inspector-plugin": "^0.20.1", "eslint": "^9.21.0", "eslint-plugin-vue": "^9.32.0", "globals": "^16.0.0", "less": "^4.2.2", "prettier": "^3.5.2", "rollup-plugin-visualizer": "^5.14.0", - "sass": "^1.85.0", + "sass": "^1.85.1", "terser": "^5.39.0", "typescript": "^5.7.3", - "vite": "^6.1.1", + "vite": "^6.2.0", "vite-plugin-cdn-import": "^1.0.1", "vite-plugin-compression2": "^1.3.3", "vite-plugin-vue-setup-extend": "^0.4.0", diff --git a/Web/src/api-services/apis/sys-wechat-pay-api.ts b/Web/src/api-services/apis/sys-wechat-pay-api.ts index 793674d2..650cc607 100644 --- a/Web/src/api-services/apis/sys-wechat-pay-api.ts +++ b/Web/src/api-services/apis/sys-wechat-pay-api.ts @@ -174,6 +174,55 @@ export const SysWechatPayApiAxiosParamCreator = function (configuration?: Config options: localVarRequestOptions, }; }, + /** + * + * @summary 获取支付订单详情(微信接口) 🔖 + * @param {string} tradeId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiSysWechatPayPayInfoFromWechatTradeIdGet: async (tradeId: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'tradeId' is not null or undefined + if (tradeId === null || tradeId === undefined) { + throw new RequiredError('tradeId','Required parameter tradeId was null or undefined when calling apiSysWechatPayPayInfoFromWechatTradeIdGet.'); + } + const localVarPath = `/api/sysWechatPay/payInfoFromWechat/{tradeId}` + .replace(`{${"tradeId"}}`, encodeURIComponent(String(tradeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, 'https://example.com'); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication Bearer required + // http bearer authentication required + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + localVarHeaderParameter["Authorization"] = "Bearer " + accessToken; + } + + const query = new URLSearchParams(localVarUrlObj.search); + for (const key in localVarQueryParameter) { + query.set(key, localVarQueryParameter[key]); + } + for (const key in options.params) { + query.set(key, options.params[key]); + } + localVarUrlObj.search = (new URLSearchParams(query)).toString(); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash, + options: localVarRequestOptions, + }; + }, /** * * @summary 获取支付订单详情 🔖 @@ -559,12 +608,56 @@ export const SysWechatPayApiAxiosParamCreator = function (configuration?: Config }, /** * - * @summary 根据支付Id获取退款信息列表 🔖 - * @param {string} [transactionId] + * @summary 微信退款回调(商户直连) 🔖 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - apiSysWechatPayRefundListGet: async (transactionId?: string, options: AxiosRequestConfig = {}): Promise => { + apiSysWechatPayRefundCallBackPost: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/sysWechatPay/refundCallBack`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, 'https://example.com'); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication Bearer required + // http bearer authentication required + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + localVarHeaderParameter["Authorization"] = "Bearer " + accessToken; + } + + const query = new URLSearchParams(localVarUrlObj.search); + for (const key in localVarQueryParameter) { + query.set(key, localVarQueryParameter[key]); + } + for (const key in options.params) { + query.set(key, options.params[key]); + } + localVarUrlObj.search = (new URLSearchParams(query)).toString(); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash, + options: localVarRequestOptions, + }; + }, + /** + * + * @summary 根据支付Id获取退款信息列表 + * @param {string} [transactionId] + * @param {string} [outTradeNumber] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiSysWechatPayRefundListGet: async (transactionId?: string, outTradeNumber?: string, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/api/sysWechatPay/refundList`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, 'https://example.com'); @@ -589,6 +682,10 @@ export const SysWechatPayApiAxiosParamCreator = function (configuration?: Config localVarQueryParameter['transactionId'] = transactionId; } + if (outTradeNumber !== undefined) { + localVarQueryParameter['outTradeNumber'] = outTradeNumber; + } + const query = new URLSearchParams(localVarUrlObj.search); for (const key in localVarQueryParameter) { query.set(key, localVarQueryParameter[key]); @@ -703,6 +800,20 @@ export const SysWechatPayApiFp = function(configuration?: Configuration) { return axios.request(axiosRequestArgs); }; }, + /** + * + * @summary 获取支付订单详情(微信接口) 🔖 + * @param {string} tradeId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { + const localVarAxiosArgs = await SysWechatPayApiAxiosParamCreator(configuration).apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId, options); + return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; + return axios.request(axiosRequestArgs); + }; + }, /** * * @summary 获取支付订单详情 🔖 @@ -816,13 +927,27 @@ export const SysWechatPayApiFp = function(configuration?: Configuration) { }, /** * - * @summary 根据支付Id获取退款信息列表 🔖 - * @param {string} [transactionId] + * @summary 微信退款回调(商户直连) 🔖 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async apiSysWechatPayRefundListGet(transactionId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { - const localVarAxiosArgs = await SysWechatPayApiAxiosParamCreator(configuration).apiSysWechatPayRefundListGet(transactionId, options); + async apiSysWechatPayRefundCallBackPost(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { + const localVarAxiosArgs = await SysWechatPayApiAxiosParamCreator(configuration).apiSysWechatPayRefundCallBackPost(options); + return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; + return axios.request(axiosRequestArgs); + }; + }, + /** + * + * @summary 根据支付Id获取退款信息列表 + * @param {string} [transactionId] + * @param {string} [outTradeNumber] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiSysWechatPayRefundListGet(transactionId?: string, outTradeNumber?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { + const localVarAxiosArgs = await SysWechatPayApiAxiosParamCreator(configuration).apiSysWechatPayRefundListGet(transactionId, outTradeNumber, options); return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; return axios.request(axiosRequestArgs); @@ -880,6 +1005,16 @@ export const SysWechatPayApiFactory = function (configuration?: Configuration, b async apiSysWechatPayPayCallBackPost(options?: AxiosRequestConfig): Promise> { return SysWechatPayApiFp(configuration).apiSysWechatPayPayCallBackPost(options).then((request) => request(axios, basePath)); }, + /** + * + * @summary 获取支付订单详情(微信接口) 🔖 + * @param {string} tradeId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId: string, options?: AxiosRequestConfig): Promise> { + return SysWechatPayApiFp(configuration).apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId, options).then((request) => request(axios, basePath)); + }, /** * * @summary 获取支付订单详情 🔖 @@ -961,13 +1096,23 @@ export const SysWechatPayApiFactory = function (configuration?: Configuration, b }, /** * - * @summary 根据支付Id获取退款信息列表 🔖 - * @param {string} [transactionId] + * @summary 微信退款回调(商户直连) 🔖 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async apiSysWechatPayRefundListGet(transactionId?: string, options?: AxiosRequestConfig): Promise> { - return SysWechatPayApiFp(configuration).apiSysWechatPayRefundListGet(transactionId, options).then((request) => request(axios, basePath)); + async apiSysWechatPayRefundCallBackPost(options?: AxiosRequestConfig): Promise> { + return SysWechatPayApiFp(configuration).apiSysWechatPayRefundCallBackPost(options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary 根据支付Id获取退款信息列表 + * @param {string} [transactionId] + * @param {string} [outTradeNumber] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiSysWechatPayRefundListGet(transactionId?: string, outTradeNumber?: string, options?: AxiosRequestConfig): Promise> { + return SysWechatPayApiFp(configuration).apiSysWechatPayRefundListGet(transactionId, outTradeNumber, options).then((request) => request(axios, basePath)); }, /** * @@ -1021,6 +1166,17 @@ export class SysWechatPayApi extends BaseAPI { public async apiSysWechatPayPayCallBackPost(options?: AxiosRequestConfig) : Promise> { return SysWechatPayApiFp(this.configuration).apiSysWechatPayPayCallBackPost(options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary 获取支付订单详情(微信接口) 🔖 + * @param {string} tradeId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SysWechatPayApi + */ + public async apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId: string, options?: AxiosRequestConfig) : Promise> { + return SysWechatPayApiFp(this.configuration).apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId, options).then((request) => request(this.axios, this.basePath)); + } /** * * @summary 获取支付订单详情 🔖 @@ -1110,14 +1266,25 @@ export class SysWechatPayApi extends BaseAPI { } /** * - * @summary 根据支付Id获取退款信息列表 🔖 - * @param {string} [transactionId] + * @summary 微信退款回调(商户直连) 🔖 * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SysWechatPayApi */ - public async apiSysWechatPayRefundListGet(transactionId?: string, options?: AxiosRequestConfig) : Promise> { - return SysWechatPayApiFp(this.configuration).apiSysWechatPayRefundListGet(transactionId, options).then((request) => request(this.axios, this.basePath)); + public async apiSysWechatPayRefundCallBackPost(options?: AxiosRequestConfig) : Promise> { + return SysWechatPayApiFp(this.configuration).apiSysWechatPayRefundCallBackPost(options).then((request) => request(this.axios, this.basePath)); + } + /** + * + * @summary 根据支付Id获取退款信息列表 + * @param {string} [transactionId] + * @param {string} [outTradeNumber] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SysWechatPayApi + */ + public async apiSysWechatPayRefundListGet(transactionId?: string, outTradeNumber?: string, options?: AxiosRequestConfig) : Promise> { + return SysWechatPayApiFp(this.configuration).apiSysWechatPayRefundListGet(transactionId, outTradeNumber, options).then((request) => request(this.axios, this.basePath)); } /** * diff --git a/Web/src/api-services/models/sys-wechat-refund.ts b/Web/src/api-services/models/sys-wechat-refund.ts index 628fc180..f6e8563a 100644 --- a/Web/src/api-services/models/sys-wechat-refund.ts +++ b/Web/src/api-services/models/sys-wechat-refund.ts @@ -106,7 +106,15 @@ export interface SysWechatRefund { * @type {string} * @memberof SysWechatRefund */ - outRefundNo: string; + outRefundNumber: string; + + /** + * 微信接口退款ID + * + * @type {string} + * @memberof SysWechatRefund + */ + refundId?: string | null; /** * 退款原因,示例:商品已售完 @@ -162,7 +170,15 @@ export interface SysWechatRefund { * @type {string} * @memberof SysWechatRefund */ - orderStatus?: string | null; + refundStatus?: string | null; + + /** + * 支完成时间 + * + * @type {Date} + * @memberof SysWechatRefund + */ + successTime?: Date | null; /** * 关联的商户商品编码 diff --git a/Web/src/components/sysDict/sysDict.vue b/Web/src/components/sysDict/sysDict.vue index d51b5ee7..6c7b8838 100644 --- a/Web/src/components/sysDict/sysDict.vue +++ b/Web/src/components/sysDict/sysDict.vue @@ -95,7 +95,7 @@ watch( diff --git a/Web/src/views/system/codeGen/component/genConfigDialog.vue b/Web/src/views/system/codeGen/component/genConfigDialog.vue index db307ae2..581b30a2 100644 --- a/Web/src/views/system/codeGen/component/genConfigDialog.vue +++ b/Web/src/views/system/codeGen/component/genConfigDialog.vue @@ -38,6 +38,9 @@ + @@ -205,6 +208,15 @@ const options = reactive({ default: 'statistical', }, }, + { + field: 'isGroupBy', + title: 'GroupBy', + minWidth: 70, + slots: { + edit: 'isGroupBy', + default: 'isGroupBy', + }, + }, { field: 'queryWhether', title: '是否是查询', diff --git a/Web/src/views/system/weChatPay/index.vue b/Web/src/views/system/weChatPay/index.vue index a1ea84af..3427e330 100644 --- a/Web/src/views/system/weChatPay/index.vue +++ b/Web/src/views/system/weChatPay/index.vue @@ -10,7 +10,7 @@ - 查询 + 查询 重置 @@ -31,10 +31,12 @@ + @@ -45,13 +47,14 @@ size="small" text type="primary" - v-if="scope.row.qrcodeContent != null && scope.row.qrcodeContent != '' && (scope.row.tradeState === '' || !scope.row.tradeState)" + v-if="scope.row.qrcodeContent != null && scope.row.qrcodeContent != '' && (scope.row.tradeState === '' || !scope.row.tradeState || scope.row.tradeState == 'NOTPAY')" @click="openQrDialog(scope.row.qrcodeContent)" > 付款二维码 查看退款 全额退款 + 刷新 @@ -75,16 +78,19 @@ 新增模拟数据 - + - + + + + - + - + @@ -134,7 +140,7 @@